BluetoothLeScanner — 用于扫描 Bluetooth Low Energy 设备的 Android 类,从 API 21(Android 5.0)开始可用。BluetoothLeScanner 取代了 BluetoothAdapter 上已过时的 startLeScan 方法,提供了灵活的 API,包括扫描配置(ScanSettings)、过滤(ScanFilter)和后台模式支持(PendingIntent)。实例通过 BluetoothAdapter.getBluetoothLeScanner() 获取。根据 Android Developers, 2026,BluetoothLeScanner 支持三种功耗模式,并允许按服务 UUID、设备名称或 MAC 地址过滤来扫描 BLE 广播包。
要点
BluetoothLeScanner — 用于在 Android 上管理 BLE 扫描的系统类。与接受简单 LeScanCallback 的 BluetoothAdapter.startLeScan() 不同,BluetoothLeScanner 提供了面向对象的 API,带有设置、过滤器和扩展错误处理。该类出现在 API 21(Android 5.0)中,同时支持 BLE 4.2,并且仍然是所有现代 Android 版本上进行 BLE 扫描的主要方式。
获取 BluetoothLeScanner 实例通过 BluetoothAdapter.getBluetoothLeScanner() 完成。如果蓝牙适配器不可用(蓝牙已禁用或设备不支持 BLE),该方法返回 null。在获取之前,检查 BluetoothAdapter.isEnabled() 以及通过 PackageManager 检查 FEATURE_BLUETOOTH_LE 的存在。获取扫描器后,您可以在任何线程中启动扫描 —— Android 自行在蓝牙堆栈的内部线程上调度 BLE 操作。
// 获取 BluetoothLeScanner
class BLEScannerManager(context: Context) {
private val bluetoothManager: BluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val adapter: BluetoothAdapter? = bluetoothManager.adapter
private var scanner: BluetoothLeScanner? = null
fun initScanner(): Boolean {
// 检查 BLE 可用性
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// 检查蓝牙是否已启用
if (adapter?.isEnabled != true) {
return false
}
// 获取扫描器
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// 检查扫描器可用性
val isAvailable: Boolean
get() = scanner != null
// 启动不带过滤器的基础扫描
fun startBasicScan() {
scanner?.startScan(object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
handleResult(result)
}
})
}
private fun handleResult(result: ScanResult) {
val device = result.device
print("Device: ${device.name ?: "Unnamed"}, RSSI: ${result.rssi}, address: ${device.address}")
}
}
BLEScannerManager 类演示了安全获取和初始化 BluetoothLeScanner。initScanner 通过 hasSystemFeature 检查 BLE 的存在、蓝牙是否已启用以及扫描器是否成功获取。startBasicScan 启动不带设置和过滤器的扫描 —— 检测范围内的所有 BLE 设备。handleResult 解析 ScanResult:BluetoothDevice(名称、地址)、RSSI(信号强度)、scanRecord(广播数据)。
ScanSettings — 用于配置 BLE 扫描的类。主要参数是扫描模式(scanMode),它决定了功耗和检测延迟之间的权衡。ScanSettings.Builder 允许配置:scanMode、callbackType(CALLBACK_TYPE_ALL_MATCHES、CALLBACK_TYPE_FIRST_MATCH、CALLBACK_TYPE_MATCH_LOST)、matchMode(MATCH_MODE_AGGRESSIVE、MATCH_MODE_STICKY)、reportDelay(批量发送延迟)和 phy(PHY_LE_1M、PHY_LE_2M、PHY_LE_CODED)。
三种扫描模式:SCAN_MODE_LOW_POWER(0)—— 低功耗后台扫描,检测延迟几秒。SCAN_MODE_BALANCED(1)—— 适用于大多数场景的平衡模式。SCAN_MODE_LOW_LATENCY(2)—— 最小检测延迟(约 100 毫秒),最大功耗。对于主动搜索设备,使用 LOW_LATENCY;对于后台监控,使用 LOW_POWER。
reportDelay —— 在批量发送结果之前的毫秒延迟。如果 reportDelay = 0,则检测后立即发送结果。如果 > 0,Android 会累积结果并通过 onBatchScanResults 发送批处理。批量发送减少了回调调用的次数并降低了功耗,适用于低优先级的后台扫描。
// 针对不同场景的 ScanSettings 配置
class ScanSettingsProvider {
// 1. 快速扫描(主动搜索)
fun lowLatencyScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setReportDelay(0)
.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
.build()
}
// 2. 节能扫描(后台监控)
fun lowPowerScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.setReportDelay(2000) // 每 2 秒一批
.build()
}
// 3. BLE Long Range 扫描(Coded PHY)
fun longRangeScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_CODED)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build()
}
// 4. 仅在 2M PHY 上扫描(BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
ScanSettingsProvider 类包含典型配置。lowLatencyScan —— 用于 UI 扫描("立即在此处"搜索)。lowPowerScan —— 用于后台监控,每 2 秒一批,回调类型为 FIRST_MATCH(仅在首次检测时激活)。longRangeScan 使用 PHY_LE_CODED(BLE Long Range,最远 1 公里)。highSpeedScan —— PHY_LE_2M(2 Mbit/s,仅限 BLE 5.0+ 设备)。
ScanFilter —— 用于过滤 BLE 扫描结果的类。没有过滤器时,BluetoothLeScanner 返回范围内的所有 BLE 设备 —— 在密集的 BLE 环境中,这可能是每分钟数百个数据包。ScanFilter 将结果缩小到所需设备,降低功耗和应用负载。过滤器在蓝牙堆栈级别应用 —— 不匹配的数据包在传递到应用程序之前被拒绝。
过滤器类型:setServiceUuid —— 服务 UUID(必须是完整的 128 位格式)。setDeviceName —— 设备名称的子字符串(区分大小写,精确子字符串匹配)。setDeviceAddress —— 精确的 MAC 地址。setManufacturerData —— 制造商数据(公司 ID + 掩码)。一次扫描可以设置多个过滤器 —— 设备必须符合所有条件(AND 逻辑)。对于 OR 逻辑,请启动多次扫描。
// 针对不同场景创建 ScanFilter
class ScanFilterFactory {
// 1. 按服务 UUID 过滤(心率监测仪)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. 按设备名称过滤("iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC-(设备)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. 组合过滤器(UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. 制造商数据
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
ScanFilterFactory 类显示所有过滤器类型。byHeartRateService 过滤具有脉搏服务 0x180D 的设备。byDeviceName 查找名称中包含 "Sensor" 的设备(Apple 建议使用唯一名称进行过滤)。byMacAddress —— 精确搜索特定设备。combinedFilter —— 按 UUID 和名称的 AND 过滤器。byManufacturer —— 按制造商数据过滤(例如,对于 iBeacon,使用 Apple 公司 ID 0x004C)。
ScanCallback —— 用于接收 BLE 扫描结果的抽象类。包含三个方法:onScanResult —— 单个结果(回调类型、ScanResult)、onBatchScanResults —— 针对 reportDelay > 0 的结果批处理、onScanFailed —— 错误代码。所有方法都在 Android 主线程上调用。对于 onScanResult 中的长时间处理,请使用协程或 HandlerThread。
ScanResult 包含:BluetoothDevice device(设备)、int rssi(信号强度,单位 dBm)、ScanRecord scanRecord(广播数据)、long timestampNanos(自系统启动以来的检测时间)。ScanRecord 提供:getServiceData() —— UUID + 自定义数据、getManufacturerSpecificData() —— 制造商数据、getAdvertiseFlags() —— BLE 标志。回调类型(callbackType)指示:CALLBACK_TYPE_ALL_MATCHES —— 与过滤器匹配、CALLBACK_TYPE_FIRST_MATCH —— 首次检测、CALLBACK_TYPE_MATCH_LOST —— 设备丢失。
onScanFailed 的错误代码:SCAN_FAILED_ALREADY_STARTED(1)—— 扫描已启动、SCAN_FAILED_APPLICATION_REGISTRATION_FAILED(2)—— 应用程序在蓝牙堆栈中的注册失败、SCAN_FAILED_INTERNAL_ERROR(3)—— 堆栈内部错误、SCAN_FAILED_FEATURE_UNSUPPORTED(4)—— 设备不支持 BLE 扫描。
// 完整扫描结果和错误处理
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. 单个结果
override fun onScanResult(callbackType: Int, result: ScanResult) {
// callbackType:1 = ALL_MATCHES,2 = FIRST_MATCH,4 = MATCH_LOST
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
onDeviceLost(result)
return
}
// 添加到列表(按地址去重)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // 更新 RSSI
} else {
results.add(result)
}
// 从广播包中提取数据
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. 批处理结果(reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. 扫描错误
override fun onScanFailed(errorCode: Int) {
val error = when (errorCode) {
ScanCallback.SCAN_FAILED_ALREADY_STARTED -> "Already scanning"
ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "Registration failed"
ScanCallback.SCAN_FAILED_INTERNAL_ERROR -> "Internal error"
ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED -> "BLE not supported"
else -> "Unknown error: $errorCode"
}
print("Error: $error")
}
}
private fun onDeviceLost(result: ScanResult) {
results.removeAll { it.device.address == result.device.address }
print("Device lost: ${result.device.address}")
}
}
ScanResultHandler 类处理蓝牙扫描仪的所有回调类型。onScanResult 更新设备列表,按 MAC 地址去重 —— 已找到的设备的 RSSI 会更新。CALLBACK_TYPE_MATCH_LOST 表示设备丢失(从列表中移除)。onBatchScanResults 处理 reportDelay > 0 的批处理结果。onScanFailed 将错误代码映射为可读消息 —— 对于 BLE 扫描调试至关重要。
PendingIntent 扫描 —— 蓝牙扫描仪的后台 BLE 扫描机制,即使在应用程序在后台时也能工作(受 Android 8+ 限制)。代替 ScanCallback,使用 PendingIntent,在检测到 BLE 设备时向系统 BroadcastReceiver 发送广播。这允许应用程序接收有关 BLE 设备的通知,而无需在内存中(系统在接收广播时创建进程)。
后台扫描限制:在 Android 8+(API 26)上,后台服务受到限制 —— PendingIntent 扫描通过 BroadcastReceiver 绕过了这一限制,系统可以在收到 BLE 事件时启动该接收器。在 Android 10+(API 29)上,后台 BLE 扫描进一步受到制造商节能策略的限制(小米、华为、三星阻止后台 BLE 操作)。对于关键的 BLE 场景,需要带有前台服务的通知。
// 通过 PendingIntent 进行后台 BLE 扫描
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// 为 BroadcastReceiver 创建 PendingIntent
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// 后台扫描设置
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// 启动后台扫描
scanner?.startScan(
null, // 过滤器
settings,
pendingIntent
)
}
fun stopBackgroundScan() {
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
scanner?.stopScan(pendingIntent)
}
}
// BroadcastReceiver BLE-
class BLEBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// 获取扫描结果
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// 向用户发送通知
showNotification(context, result.device.name ?: " ")
}
}
}
private fun showNotification(context: Context, name: String) {
val notification = Notification.Builder(context, "ble_channel")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentTitle("BLE devices")
.setContentText("Found: $name")
.setAutoCancel(true)
.build()
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
manager.notify(System.currentTimeMillis().toInt(), notification)
}
}
BackgroundBLEScanner 类通过 PendingIntent 启动后台 BLE 扫描。startBackgroundScan 创建一个 PendingIntent,当检测到 BLE 设备时,它会向 BLEBroadcastReceiver 发送广播。BroadcastReceiver 通过 getPendingIntentScanResults() 提取 ScanResult,并可以显示通知或将数据发送到服务器。即使应用程序已被系统终止,这种方法也能工作 —— Android 在接收广播时启动 BroadcastReceiver。
完整示例 在 Kotlin 中的 BLE 扫描器,使用 BluetoothLeScanner 和 ScanSettings、ScanFilter、ScanCallback 来查找心率监测仪设备。扫描器显示找到的设备列表,包含 RSSI 和服务 UUID,并可通过 BluetoothGatt 进行连接。
// 在 Kotlin 中使用协程的完整 BLE 扫描器
class DeviceScanner(private val context: Context) {
private val adapter: BluetoothAdapter? by lazy {
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE)
as BluetoothManager
manager.adapter
}
private val scanner: BluetoothLeScanner? by lazy {
adapter?.bluetoothLeScanner
}
fun startScan(duration: Long = 10000): Flow<ScanResult> = callbackFlow {
// 检查蓝牙状态
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// 扫描配置
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val filters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
.build()
)
val callback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
trySend(result)
}
override fun onScanFailed(errorCode: Int) {
close(BLEException("Scan failed: $errorCode"))
}
}
// 开始扫描
scanner?.startScan(filters, settings, callback)
// 持续时间后自动停止
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
DeviceScanner 类使用 Kotlin Flow(callbackFlow)进行响应式 BLE 扫描。扫描使用 LOW_LATENCY 设置和 Heart Rate Service UUID 过滤器启动。结果通过 onScanResult 发送到 Flow。在指定持续时间后自动停止(默认为 10 秒)。FlowOn(Dispatchers.IO) 将 BLE 操作移动到后台线程。这种方法允许在 MVVM 架构中通过 viewModelScope.launch 和 collect 使用 BLE 扫描。
常见问题
BluetoothLeScanner —— 用于 BLE 扫描的 Android 类(API 21+)。通过 BluetoothAdapter.getBluetoothLeScanner() 获取。支持三种扫描模式(LOW_POWER、BALANCED、LOW_LATENCY),按 UUID、名称和 MAC 地址过滤,批处理结果以及用于后台扫描的 PendingIntent。替代已过时的 BluetoothAdapter.startLeScan() 方法。
SCAN_MODE_LOW_POWER —— 后台模式,检测延迟 5–10 秒,功耗最低。SCAN_MODE_LOW_LATENCY —— 主动模式,延迟约 100 毫秒,功耗最高。SCAN_MODE_BALANCED —— 折衷方案(约 2 秒延迟)。对于 UI 扫描,使用 LOW_LATENCY;对于后台监控,使用 LOW_POWER 和 PendingIntent。
原因:蓝牙已禁用(检查 adapter.isEnabled)、未授予权限(API 31+ 上的 BLUETOOTH_SCAN、API 23–30 上的 ACCESS_FINE_LOCATION)、scanner = null(适配器不可用)、设备超出范围或使用了错误的过滤器。同时检查 onScanFailed —— 错误代码将指示原因:SCAN_FAILED_ALREADY_STARTED(1)或 SCAN_FAILED_APPLICATION_REGISTRATION_FAILED(2)。
使用 PendingIntent 版本的 startScan() —— 传递 PendingIntent 而不是 ScanCallback。当检测到 BLE 设备时,Android 向 BroadcastReceiver 发送广播,即使应用程序在后台,系统也可以启动该接收器。对于 Android 8+,将 BroadcastReceiver 添加到清单中。在 Android 10+ 上,考虑制造商的节能限制。
BluetoothLeScanner 对可检测设备数量没有限制 —— 限制取决于环境的 BLE 饱和度。办公室中可能有 20–50 个活动 BLE 设备,购物中心可能有数百个。对于过滤,使用 ScanFilter(按 UUID、名称)。不过滤时,异步处理结果 —— onScanResult 可能每秒被调用数十次。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。