Android 12及以上版本蓝牙设备发现功能失效问题求助
Android 12 蓝牙设备发现适配解决方案
核心问题定位
Android 12(API 31)对蓝牙扫描的权限体系、广播接收机制做了针对性限制,即便bluetoothAdapter.startDiscovery()执行成功,也会因权限缺失或广播注册方式错误导致无法接收设备发现事件。
一、权限适配(必做)
除旧版本的BLUETOOTH、BLUETOOTH_ADMIN权限外,Android 12+需补充以下权限:
BLUETOOTH_SCAN:蓝牙扫描专属权限,必须运行时申请ACCESS_FINE_LOCATION:扫描非配对设备时必须申请(系统将蓝牙扫描结果与位置信息绑定)
权限声明(AndroidManifest.xml)
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-feature android:name="android.hardware.bluetooth" android:required="true" />
运行时权限申请代码
private val REQUEST_BLUETOOTH_PERM = 1001 private fun checkAndRequestPermissions() { val requiredPermissions = arrayOf( Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_FINE_LOCATION ) val needRequest = requiredPermissions.any { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } if (needRequest) { ActivityCompat.requestPermissions(this, requiredPermissions, REQUEST_BLUETOOTH_PERM) } else { startDiscovery() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_BLUETOOTH_PERM && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { startDiscovery() } }
二、BroadcastReceiver 注册方式修正
Android 12+ 禁止通过Manifest静态注册接收BluetoothDevice.ACTION_FOUND这类隐式广播,必须动态注册。
错误示例(Manifest注册,Android12+无效)
<!-- 此方式在Android12及以上版本无法接收设备发现事件 --> <receiver android:name=".BluetoothReceiver"> <intent-filter> <action android:name="android.bluetooth.device.action.FOUND" /> </intent-filter> </receiver>
正确示例(动态注册)
private lateinit var discoveryReceiver: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化广播接收器 discoveryReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { intent ?: return when(intent.action) { BluetoothDevice.ACTION_FOUND -> { // 获取发现的蓝牙设备 val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) device?.let { Log.d("BluetoothTest", "发现设备:${it.name ?: "未知设备"} | ${it.address}") // 此处可将设备添加至UI列表 } } BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> { Log.d("BluetoothTest", "扫描结束") } } } } // 注册广播过滤器 val filter = IntentFilter().apply { addAction(BluetoothDevice.ACTION_FOUND) addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) } registerReceiver(discoveryReceiver, filter) } override fun onDestroy() { super.onDestroy() // 必须注销广播,避免内存泄漏 unregisterReceiver(discoveryReceiver) // 销毁时停止扫描 if (bluetoothAdapter.isDiscovering) { bluetoothAdapter.cancelDiscovery() } }
三、完整适配示例代码
主Activity核心逻辑
class BluetoothScanActivity : AppCompatActivity() { private val REQUEST_BLUETOOTH_PERM = 1001 private lateinit var bluetoothAdapter: BluetoothAdapter private lateinit var discoveryReceiver: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_bluetooth_scan) // 初始化蓝牙适配器 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: run { Toast.makeText(this, "设备不支持蓝牙", Toast.LENGTH_SHORT).show() finish() return } // 初始化广播接收器 initDiscoveryReceiver() // 注册广播 registerReceiver(discoveryReceiver, IntentFilter().apply { addAction(BluetoothDevice.ACTION_FOUND) addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) }) // 检查蓝牙状态 if (!bluetoothAdapter.isEnabled) { startActivityForResult(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_BLUETOOTH_PERM) } else { checkAndRequestPermissions() } } private fun initDiscoveryReceiver() { discoveryReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { intent ?: return when(intent.action) { BluetoothDevice.ACTION_FOUND -> { val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) device?.let { Log.d("BluetoothScan", "设备:${it.name ?: "无名"} | ${it.address}") } } BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> { Toast.makeText(context, "扫描完成", Toast.LENGTH_SHORT).show() } } } } } private fun startDiscovery() { if (bluetoothAdapter.isDiscovering) { bluetoothAdapter.cancelDiscovery() } val isStarted = bluetoothAdapter.startDiscovery() Log.d("BluetoothScan", "扫描启动状态:$isStarted") if (!isStarted) { Toast.makeText(this, "扫描启动失败", Toast.LENGTH_SHORT).show() } } private fun checkAndRequestPermissions() { val requiredPermissions = arrayOf( Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_FINE_LOCATION ) val needRequest = requiredPermissions.any { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } if (needRequest) { ActivityCompat.requestPermissions(this, requiredPermissions, REQUEST_BLUETOOTH_PERM) } else { startDiscovery() } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_BLUETOOTH_PERM && resultCode == RESULT_OK) { checkAndRequestPermissions() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_BLUETOOTH_PERM && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { startDiscovery() } else { Toast.makeText(this, "权限不足,无法扫描蓝牙设备", Toast.LENGTH_SHORT).show() } } override fun onDestroy() { super.onDestroy() unregisterReceiver(discoveryReceiver) if (bluetoothAdapter.isDiscovering) { bluetoothAdapter.cancelDiscovery() } } }
四、Android 12+ BroadcastReceiver 关键注意事项
- 隐式广播限制:除少数系统白名单广播外,大部分隐式广播(如
ACTION_FOUND)禁止静态注册,必须动态绑定到组件生命周期 - 权限范围细分:
BLUETOOTH_SCAN可添加android:usesPermissionFlags="neverForLocation",无需位置权限,但仅能扫描已配对设备 - 后台扫描限制:应用在后台时扫描频率会被系统限制,需申请
ACCESS_BACKGROUND_LOCATION权限才能正常后台扫描 - 生命周期绑定:动态注册的广播必须在组件销毁时注销,否则会引发内存泄漏
内容的提问来源于stack exchange,提问作者Naiara Aguirre
相关产品推荐
相关产品推荐

