Android开发:是否存在无需回调的BLE设备扫描方法?
嘿,我来帮你解决这个BLE扫描的问题!
首先明确一点:Android原生的BLE API并没有直接同步返回扫描结果的函数——因为BLE扫描本质是异步操作,系统在后台处理设备探测,没法直接阻塞线程等待结果返回。你遇到的回调方案不好用的问题,其实可以通过封装异步逻辑来解决,让调用起来更像“同步获取列表”的感觉,同时适配你每秒多次扫描的需求。
下面给你两个实用的方案,都是基于原生API优化的:
方案1:用协程封装,把异步回调转成挂起函数(推荐)
如果你的项目用Kotlin,协程可以完美解决异步回调的痛点,把扫描逻辑封装成一个挂起函数,调用时就能直接拿到扫描结果列表,就像同步方法一样。
核心思路:
- 用
CompletableDeferred来等待扫描结束 - 启动扫描后,在回调里收集设备结果
- 扫描指定时长(比如1秒)后停止扫描,返回结果列表
代码示例:
import kotlinx.coroutines.* import android.bluetooth.le.* import android.bluetooth.BluetoothAdapter import java.util.concurrent.CopyOnWriteArrayList class BleScannerManager { private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter() private val bleScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner private var activeScanCallback: ScanCallback? = null // 用线程安全的列表存扫描结果,避免多线程问题 private val scanResults = CopyOnWriteArrayList<ScanResult>() /** * 扫描BLE设备,指定扫描时长后返回结果列表 * @param scanDurationMs 扫描时长(默认1000ms) * @return 扫描到的设备列表 */ suspend fun scanBleDevices(scanDurationMs: Long = 1000): List<ScanResult> { return withContext(Dispatchers.IO) { val resultDeferred = CompletableDeferred<List<ScanResult>>() // 清空上次的扫描结果 scanResults.clear() // 创建扫描回调 activeScanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { super.onScanResult(callbackType, result) // 避免重复添加同一设备(可以根据设备地址判断) if (!scanResults.any { it.device.address == result.device.address }) { scanResults.add(result) } } override fun onScanFailed(errorCode: Int) { super.onScanFailed(errorCode) resultDeferred.completeExceptionally( RuntimeException("BLE扫描失败,错误码:$errorCode") ) } } try { // 开始扫描 bleScanner?.startScan(activeScanCallback) // 等待指定时长 delay(scanDurationMs) // 停止扫描 bleScanner?.stopScan(activeScanCallback) // 返回结果副本 resultDeferred.complete(scanResults.toList()) } catch (e: Exception) { resultDeferred.completeExceptionally(e) } finally { activeScanCallback = null } resultDeferred.await() } } /** * 主动停止扫描(比如页面销毁时调用) */ fun stopScan() { activeScanCallback?.let { bleScanner?.stopScan(it) } activeScanCallback = null scanResults.clear() } }
调用方式(在Activity/ViewModel里):
val bleScannerManager = BleScannerManager() // 每秒扫描一次的示例 lifecycleScope.launch { while (isActive) { try { val devices = bleScannerManager.scanBleDevices(1000) // 处理扫描到的设备列表 println("扫描到${devices.size}个BLE设备:${devices.map { it.device.name ?: it.device.address }}") } catch (e: Exception) { println("扫描出错:${e.message}") } // 这里可以根据需求调整间隔,比如扫描1秒,休息0秒就是每秒一次 delay(0) } } // 页面销毁时记得停止扫描 override fun onDestroy() { super.onDestroy() bleScannerManager.stopScan() }
方案2:用定时器+线程安全列表(Java/Kotlin通用)
如果不用协程,也可以手动管理扫描状态:每次启动扫描前清空结果列表,设置定时器在指定时长后停止扫描,然后读取列表。
核心步骤:
- 初始化一个线程安全的列表(比如
CopyOnWriteArrayList)存储扫描结果 - 启动扫描时清空列表,注册回调添加设备
- 用
Handler或Timer在1秒后停止扫描,然后处理列表中的结果
重要注意事项:
- 权限问题:必须申请对应的权限:
- API 31+:
BLUETOOTH_SCAN、BLUETOOTH_ADMIN,如果不需要位置信息,要在权限里加android:usesPermissionFlags="neverForLocation" - API 23-30:
ACCESS_FINE_LOCATION(或ACCESS_COARSE_LOCATION)、BLUETOOTH_ADMIN
- API 31+:
- 蓝牙状态检查:扫描前要确保蓝牙已开启,可通过
bluetoothAdapter?.isEnabled判断,未开启时可以跳转到蓝牙设置页面 - 耗电与系统限制:每秒扫描一次频率很高,会增加设备耗电,部分Android系统可能会限制频繁扫描的行为,建议根据实际需求调整扫描间隔
- 内存泄漏:一定要在不需要扫描时(比如页面销毁)停止扫描,释放回调引用
内容的提问来源于stack exchange,提问作者user9841984
相关产品推荐
相关产品推荐

