避开这5个坑!Android蓝牙广播接收的常见错误及正确姿势
避开这5个坑Android蓝牙广播接收的常见错误及正确姿势蓝牙技术已经成为现代移动应用不可或缺的一部分从无线耳机到智能家居设备蓝牙连接无处不在。然而在Android开发中处理蓝牙广播却是一个让许多开发者头疼的问题。错误的广播接收实现不仅会导致功能异常还可能引发性能问题和电池消耗过快。本文将深入剖析开发者最容易犯的五个蓝牙广播接收错误并提供经过实战验证的解决方案。1. 广播接收器注册与注销的时机错误很多开发者在使用蓝牙广播时最常见的错误就是没有正确管理广播接收器的生命周期。不当的注册和注销时机会导致内存泄漏、重复接收广播或者完全错过重要事件。1.1 动态注册与静态注册的选择在Android中广播接收器可以通过两种方式注册静态注册在AndroidManifest.xml中声明和动态注册在代码中使用registerReceiver()。对于蓝牙广播我们强烈建议使用动态注册原因如下蓝牙广播通常是应用运行时特定的不需要在应用未启动时接收动态注册可以更精确地控制接收的广播类型减少不必要的处理静态注册在Android 8.0及以上版本对隐式广播有限制// 正确的动态注册示例 private val bluetoothStateReceiver object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when(intent.action) { BluetoothAdapter.ACTION_STATE_CHANGED - { val state intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) handleBluetoothStateChange(state) } } } } override fun onStart() { super.onStart() val filter IntentFilter().apply { addAction(BluetoothAdapter.ACTION_STATE_CHANGED) } registerReceiver(bluetoothStateReceiver, filter) } override fun onStop() { super.onStop() unregisterReceiver(bluetoothStateReceiver) }1.2 注册与注销的最佳实践在实际开发中我们还需要注意以下几点在Activity的onResume()和onPause()中注册/注销而不是onCreate()/onDestroy()这样可以更好地处理配置变更对于Service中的广播接收器确保在服务启动时注册在服务停止时注销使用弱引用或处理程序来避免因广播接收器持有Activity引用导致的内存泄漏2. 遗漏必要的蓝牙权限Android的权限系统不断演进蓝牙相关权限也在变化。很多开发者在使用蓝牙功能时要么声明了错误的权限要么完全忘记了声明权限。2.1 必须声明的基本权限在AndroidManifest.xml中以下权限是蓝牙功能必需的uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN /对于Android 12及以上版本还需要添加uses-permission android:nameandroid.permission.BLUETOOTH_SCAN / uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT /2.2 运行时权限处理从Android 6.0开始部分权限需要在运行时请求。对于蓝牙相关权限处理方式如下权限类型是否需要运行时请求最低API级别BLUETOOTH否所有版本BLUETOOTH_ADMIN否所有版本BLUETOOTH_SCAN是Android 1231BLUETOOTH_CONNECT是Android 1231ACCESS_FINE_LOCATION是Android 6.0-1123// 检查并请求蓝牙权限的示例 private fun checkBluetoothPermissions() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { when { ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) PackageManager.PERMISSION_GRANTED - { // 权限已授予可以执行蓝牙操作 } else - { // 请求权限 ActivityCompat.requestPermissions( this, arrayOf( Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT ), BLUETOOTH_PERMISSION_REQUEST_CODE ) } } } else if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { // Android 6.0到11需要位置权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE ) } } }3. 混淆配对状态与连接状态蓝牙设备的配对(Bonding)和连接(Connection)是两个完全不同的概念但很多开发者经常将它们混淆导致逻辑错误。3.1 配对(Bonding)与连接(Connection)的区别特性配对(Bonding)连接(Connection)目的建立安全信任关系建立数据传输通道持久性持久保存直到手动取消临时建立断开后消失广播事件ACTION_BOND_STATE_CHANGEDACTION_ACL_CONNECTED/DISCONNECTED状态值BOND_NONE, BOND_BONDING, BOND_BONDEDSTATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED3.2 正确处理状态变化在代码中我们需要分别处理这两种状态的变化// 配对状态变化处理 private val pairingReceiver object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when(intent.action) { BluetoothDevice.ACTION_BOND_STATE_CHANGED - { val device intent.getParcelableExtraBluetoothDevice(BluetoothDevice.EXTRA_DEVICE) val previousState intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR) val state intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR) when(state) { BluetoothDevice.BOND_BONDED - { // 设备已配对可以尝试连接 connectToDevice(device) } BluetoothDevice.BOND_NONE - { if (previousState BluetoothDevice.BOND_BONDING) { // 配对失败处理 showPairingFailed(device) } } } } } } } // 连接状态变化处理 private val connectionReceiver object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val device intent.getParcelableExtraBluetoothDevice(BluetoothDevice.EXTRA_DEVICE) when(intent.action) { BluetoothDevice.ACTION_ACL_CONNECTED - { // 设备已连接可以开始数据传输 startDataTransfer(device) } BluetoothDevice.ACTION_ACL_DISCONNECTED - { // 设备断开连接进行清理工作 cleanupConnection(device) } } } }4. 忽略蓝牙适配器的状态检查在尝试使用蓝牙功能前不检查蓝牙适配器的状态是另一个常见错误。这会导致在蓝牙不可用时出现各种异常。4.1 完整的蓝牙状态检查流程获取BluetoothAdapter实例val bluetoothAdapter: BluetoothAdapter? BluetoothAdapter.getDefaultAdapter() if (bluetoothAdapter null) { // 设备不支持蓝牙 showUnsupportedError() return }检查蓝牙是否启用if (!bluetoothAdapter.isEnabled) { // 蓝牙未启用请求用户开启 val enableBtIntent Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) }监听蓝牙状态变化val filter IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) registerReceiver(bluetoothStateReceiver, filter) private val bluetoothStateReceiver object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { BluetoothAdapter.STATE_ON - { // 蓝牙已开启可以继续操作 setupBluetooth() } BluetoothAdapter.STATE_OFF - { // 蓝牙已关闭清理资源 cleanupBluetoothResources() } } } }4.2 处理不同蓝牙功能可用性除了基本的启用状态还需要检查特定蓝牙功能是否可用// 检查BLE(蓝牙低功耗)支持 if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { showToast(设备不支持BLE功能) } // 检查蓝牙5.0支持 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O !bluetoothAdapter.isLe2MPhySupported) { showToast(设备不支持蓝牙5.0) }5. 不当处理设备发现过程蓝牙设备发现是一个耗电且耗时的操作不当的处理会导致性能问题和糟糕的用户体验。5.1 设备发现的最佳实践合理控制发现时长// 开始发现设备 if (bluetoothAdapter.startDiscovery()) { // 设置12秒后自动停止发现 handler.postDelayed({ bluetoothAdapter.cancelDiscovery() }, 12000) }正确处理发现广播private val discoveryReceiver object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when(intent.action) { BluetoothDevice.ACTION_FOUND - { val device intent.getParcelableExtraBluetoothDevice(BluetoothDevice.EXTRA_DEVICE) val rssi intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE) addDiscoveredDevice(device, rssi) } BluetoothAdapter.ACTION_DISCOVERY_STARTED - { showDiscoveryStarted() } BluetoothAdapter.ACTION_DISCOVERY_FINISHED - { showDiscoveryFinished() } } } }发现前后的必要检查fun startSafeDiscovery() { if (bluetoothAdapter.isDiscovering) { bluetoothAdapter.cancelDiscovery() } // 检查位置权限Android 6.0需要 if (checkLocationPermission()) { if (!bluetoothAdapter.startDiscovery()) { showToast(无法开始设备发现) } } }5.2 设备发现优化技巧缓存已发现设备避免重复处理同一设备按RSSI过滤设备只显示信号强度足够的设备使用蓝牙LE扫描API针对BLE设备更高效且耗电更少在适当的时候取消发现如已找到目标设备时// BLE扫描示例Android 5.0 private val bleScanner bluetoothAdapter.bluetoothLeScanner private val bleScanCallback object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { super.onScanResult(callbackType, result) processBleDevice(result.device, result.rssi) } } fun startBleScan() { val settings ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build() val filters listOfScanFilter() // 可以添加过滤条件 bleScanner.startScan(filters, settings, bleScanCallback) // 10秒后停止扫描 handler.postDelayed({ stopBleScan() }, 10000) } fun stopBleScan() { bleScanner.stopScan(bleScanCallback) }在实际项目中我发现正确处理蓝牙广播的关键在于三点精确控制接收器生命周期、全面处理各种状态变化、优化资源使用效率。遵循这些原则可以构建出稳定高效的蓝牙功能模块。