You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

BLE周期性扫描设备计数代码合理性及扫描机制疑问

问题描述

我正在开发一款应用,计划每5秒执行一次BLE扫描,向用户展示周边的设备数量,且已确认用户授予了全部所需权限。

示例代码

class ExampleBle(private val viewModel: Mainviewmodel) {

    private val deviceAddresses = arrayListOf<String>()

    private val bleAdapter: BluetoothAdapter by lazy {
        val b = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        b.adapter
    }

    private val bleScan : BluetoothLeScanner by lazy {
        bleAdapter.bluetoothLeScanner
    }

    private val scanSettings = ScanSettings.Builder()
        //Questo tipo di scan è usato per scan di lunga durata e spreca meno energia
        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
        .build()


    private val scanCallback = object : ScanCallback() {
        @SuppressLint("MissingPermission")
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            deviceAddresses.add(result.device.address)
        }

        override fun onScanFailed(errorCode: Int) {
            Log.e("ScanCallback", "onScanFailed: code $errorCode")
        }
    }
    //function to repeat every 5 seconds
    fun startScan(){
        bleScan.startScan(null, scanSettings, scanCallback)

        // at this point the deviceAddresses list contains the addresses
        // of all the devices around
        viewModel.updateNumberDevices(deviceAddresses.size)
        
        //clear the list to recollect all the device addresses from the next scan after 5 seconds
        deviceAddresses.clear()
    }
}

预期流程:调用startScan()后启动BLE扫描,通过scanCallback将周边设备地址加入列表,最终展示设备数量。

疑问

  1. 若周边存在100台设备,5秒内的扫描能否返回所有设备地址?
  2. 若前一次扫描未完成就启动新扫描,设备列表是否会出现重复地址等异常?
  3. 希望直观了解BLE扫描的实际工作机制。

解答

代码是否符合需求?

完全不符合,核心逻辑存在致命问题:

  • bleScan.startScan()是异步调用,调用后立刻返回,不会等待扫描完成。你在调用后直接读取deviceAddresses.size,此时扫描才刚启动,列表基本是空的,根本拿不到有效结果。
  • 没有实现扫描停止逻辑,每次调用startScan()都会开启新扫描,旧扫描不会自动终止,导致多扫描并行,结果混乱且耗电。
  • 代码本身没有“每5秒执行一次扫描”的定时逻辑,startScan()只是单次调用。

疑问解答

1. 5秒内能否扫完100台设备?

不一定。BLE设备是周期性广播(间隔从几十毫秒到几秒不等),扫描器需要轮询3个BLE广播信道才能接收广播包:

  • 如果设备广播间隔短(比如100ms),5秒内大概率能扫到所有设备;但如果部分设备广播间隔超过5秒,那肯定扫不到。
  • 100台设备的场景下,系统蓝牙模块负载会升高,加上环境干扰、信号强弱等因素,部分设备的广播包可能被遗漏。

2. 前一次扫描未完成就启动新扫描会怎样?

直接多次调用startScan()会导致多个扫描任务同时运行,引发以下问题:

  • 同一个设备的广播包会被多个扫描回调重复接收,deviceAddresses会出现大量重复地址,统计的数量远大于实际设备数。
  • 蓝牙模块负载过高,扫描效率下降,甚至可能触发系统扫描限制导致扫描失败。
  • 电量消耗急剧增加,违背BLE低功耗的设计初衷。

BLE扫描的实际工作机制

BLE扫描本质是监听设备的广播包,核心流程如下:

  1. 信道轮询:BLE有3个专属广播信道(37、38、39),扫描器会周期性轮询这三个信道,每个信道停留一段时间接收广播包。
  2. 异步回调:扫描器收到广播包后,会通过ScanCallback的onScanResult()异步通知应用,这个过程和调用startScan()的线程完全分离。
  3. 手动启停:扫描必须调用stopScan()手动终止,否则会一直运行(除非系统因资源限制强制停止)。
  4. 广播特性:BLE设备不是持续广播,而是按设定间隔发送广播包——间隔越短,被扫描到的概率越高,但设备自身功耗也越大。

修正建议

要实现你的需求,需要调整核心逻辑:

  1. 每次扫描前先停止之前的扫描,避免多扫描并行。
  2. 使用定时任务(比如Handler、协程)每5秒触发一次扫描,每次扫描持续固定时间(比如4秒),停止后统计去重后的设备数量。
  3. 对设备地址去重,因为同一个设备可能多次触发onScanResult()。

修正后的核心逻辑示例:

private val handler = Handler(Looper.getMainLooper())
private val scanRunnable = object : Runnable {
    override fun run() {
        // 停止之前的扫描并清空列表
        bleScan.stopScan(scanCallback)
        deviceAddresses.clear()
        
        // 启动新扫描
        bleScan.startScan(null, scanSettings, scanCallback)
        
        // 4秒后停止扫描,统计去重后的设备数
        handler.postDelayed({
            bleScan.stopScan(scanCallback)
            val uniqueDeviceCount = deviceAddresses.toSet().size
            viewModel.updateNumberDevices(uniqueDeviceCount)
            // 5秒后再次执行扫描
            handler.postDelayed(this, 5000)
        }, 4000)
    }
}

// 启动周期性扫描
fun startPeriodicScan() {
    scanRunnable.run()
}

// 停止周期性扫描
fun stopPeriodicScan() {
    handler.removeCallbacks(scanRunnable)
    bleScan.stopScan(scanCallback)
}

内容的提问来源于stack exchange,提问作者Daniele Mazzei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 18:41:12