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

蓝牙BLE自定义扫描延迟设置方法咨询

解决BLE扫描间隔控制问题

要实现按用户设置的间隔(秒数)扫描BLE标签,核心是周期性启停扫描,而非持续扫描。持续扫描会导致设备频繁回调,出现每秒多个设备的情况。以下是具体实现方案:

核心思路

  1. 用协程替代TimerTask,适配Android生命周期,避免内存泄漏
  2. 每次仅启动极短时间的扫描(比如100ms),获取当前可检测设备后立即停止
  3. 等待用户设置的间隔时间后,重复扫描动作
  4. 维护扫描状态,避免重复启动任务

修改后的完整代码

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()
    }
}

补充说明

  1. 扫描时长调整:代码中delay(100)控制单次扫描的时长,可根据需求修改(比如200ms),确保能获取到当前范围内的设备
  2. 间隔单位适配:若要用户输入秒数,可在设置对话框中将输入值乘以1000后再保存
  3. 权限检查:确保在AndroidManifest.xml中声明必要权限,Android 12+需要BLUETOOTH_SCAN、BLUETOOTH_CONNECT,Android 11及以下需要ACCESS_FINE_LOCATION
  4. 设备去重:通过deviceMap按设备地址去重,避免重复添加同一设备

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 03:55:22