蓝牙BLE自定义扫描延迟设置方法咨询
解决BLE扫描间隔控制问题
要实现按用户设置的间隔(秒数)扫描BLE标签,核心是周期性启停扫描,而非持续扫描。持续扫描会导致设备频繁回调,出现每秒多个设备的情况。以下是具体实现方案:
核心思路
- 用协程替代TimerTask,适配Android生命周期,避免内存泄漏
- 每次仅启动极短时间的扫描(比如100ms),获取当前可检测设备后立即停止
- 等待用户设置的间隔时间后,重复扫描动作
- 维护扫描状态,避免重复启动任务
修改后的完整代码
class BluetoothActivity : AppCompatActivity(), MenuProvider { private lateinit var recyclerView: RecyclerView private var bluetoothLeScanner: BluetoothLeScanner? = null private val deviceList = mutableListOf<BleTag>() private val deviceMap = mutableMapOf<String, BleTag>() private var interval: Long = 1000 private lateinit var menu: Menu private lateinit var tagBinding: ListbluethoothBinding private var isScanning = false private var scanJob: Job? = null private val scanCallback = object : ScanCallback() { @RequiresApi(Build.VERSION_CODES.N) @SuppressLint("MissingPermission", "NotifyDataSetChanged") override fun onScanResult(callbackType: Int, result: ScanResult) { CoroutineScope(Dispatchers.Main).launch { val tag = deviceMap.computeIfAbsent(result.device.address) { val newTag = BleTag( result.device.name ?: "未知设备", result.device.address, result.rssi, result.scanRecord?.bytes, "" ) deviceList.add(newTag) newTag } tag.name = result.device.name ?: "未知设备" tag.rssi = result.rssi tag.advertisementData = result.scanRecord?.bytes deviceList.sortBy { it.rssi } recyclerView.adapter?.notifyDataSetChanged() menu.findItem(R.id.count).title = "设备数: ${deviceList.size}" } super.onScanResult(callbackType, result) } override fun onScanFailed(errorCode: Int) { super.onScanFailed(errorCode) Log.e("扫描失败", "错误码: $errorCode") } } @SuppressLint("MissingPermission") @RequiresApi(Build.VERSION_CODES.M) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) tagBinding = ListbluethoothBinding.inflate(layoutInflater) setContentView(tagBinding.root) recyclerView = tagBinding.list recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = RecyclerViewAdapter(deviceList) addMenuProvider(this) val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java) val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner // 从SharedPreferences读取保存的间隔 interval = getSharedPreferences("interval", MODE_PRIVATE).getLong("interval", 1000) } // 启动周期性扫描任务 private fun startPeriodicScan() { scanJob = CoroutineScope(Dispatchers.IO).launch { while (isActive) { if (!isScanning) { runOnUiThread { bluetoothLeScanner?.startScan(scanCallback) isScanning = true } // 扫描100ms后停止,避免一次性获取过多设备 delay(100) runOnUiThread { bluetoothLeScanner?.stopScan(scanCallback) isScanning = false } } // 等待用户设置的间隔时间 delay(interval) } } } // 停止扫描并取消任务 private fun stopScanIfNeeded() { scanJob?.cancel() if (isScanning) { bluetoothLeScanner?.stopScan(scanCallback) isScanning = false } } override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { this.menu = menu menuInflater.inflate(R.menu.items, menu) } @RequiresApi(Build.VERSION_CODES.M) @SuppressLint("MissingPermission") override fun onMenuItemSelected(menuItem: MenuItem): Boolean { return when (menuItem.itemId) { R.id.stop -> { stopScanIfNeeded() Snackbar.make(findViewById(android.R.id.content), "扫描已停止", 2000).show() true } R.id.start -> { stopScanIfNeeded() startPeriodicScan() Snackbar.make(findViewById(android.R.id.content), "扫描已启动", 2000).show() true } R.id.settings -> { // 弹出间隔设置对话框 val inputView = layoutInflater.inflate(R.layout.dialog_interval_input, null) val intervalInput = inputView.findViewById<EditText>(R.id.interval_input) intervalInput.setText(interval.toString()) AlertDialog.Builder(this) .setTitle("设置扫描间隔") .setView(inputView) .setPositiveButton("确定") { dialog, _ -> val inputText = intervalInput.text.toString() interval = inputText.toLongOrNull() ?: 1000 // 保存到SharedPreferences getSharedPreferences("interval", MODE_PRIVATE) .edit() .putLong("interval", interval) .apply() // 重启扫描任务应用新间隔 stopScanIfNeeded() startPeriodicScan() Snackbar.make(findViewById(android.R.id.content), "间隔已设置为${interval}ms", 2000).show() dialog.dismiss() } .setNegativeButton("取消") { dialog, _ -> dialog.dismiss() } .create() .show() true } else -> false } } override fun onDestroy() { super.onDestroy() stopScanIfNeeded() } }
补充说明
- 扫描时长调整:代码中
delay(100)控制单次扫描的时长,可根据需求修改(比如200ms),确保能获取到当前范围内的设备 - 间隔单位适配:若要用户输入秒数,可在设置对话框中将输入值乘以1000后再保存
- 权限检查:确保在AndroidManifest.xml中声明必要权限,Android 12+需要
BLUETOOTH_SCAN、BLUETOOTH_CONNECT,Android 11及以下需要ACCESS_FINE_LOCATION - 设备去重:通过
deviceMap按设备地址去重,避免重复添加同一设备
内容的提问来源于stack exchange,提问作者Master4You
相关产品推荐
相关产品推荐

