Android 12蓝牙开发深度指南权限、虚拟地址与真实MAC获取实战在移动应用开发领域蓝牙技术一直是连接智能设备的重要桥梁。然而随着Android系统版本的迭代更新特别是从Android 12API 31开始蓝牙权限管理体系发生了重大变革这让不少开发者措手不及。许多人在完成基础功能开发后突然发现设备扫描失败或者获取到的MAC地址变成了毫无意义的02:00:00:00:00:00虚拟地址。1. Android 12蓝牙权限体系重构Android 12对蓝牙权限进行了彻底的重构将原本单一的BLUETOOTH和BLUETOOTH_ADMIN权限拆分为三个更细粒度的权限uses-permission android:nameandroid.permission.BLUETOOTH_SCAN / uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / uses-permission android:nameandroid.permission.BLUETOOTH_ADVERTISE /这三个权限分别对应不同的蓝牙操作场景BLUETOOTH_SCAN用于发现和扫描附近的蓝牙设备BLUETOOTH_CONNECT用于连接已配对的蓝牙设备BLUETOOTH_ADVERTISE用于让设备可被发现1.1 动态权限申请的必要性与之前的版本不同Android 12要求这些权限必须通过运行时动态申请而不仅仅是在AndroidManifest.xml中静态声明。这意味着开发者需要在代码中主动请求用户授权val permissions arrayOf( Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.ACCESS_FINE_LOCATION ) ActivityCompat.requestPermissions( this, permissions, REQUEST_CODE_BLUETOOTH_PERMISSIONS )注意即使你的应用只需要扫描而不需要连接蓝牙设备也必须申请BLUETOOTH_SCAN权限否则扫描操作将无法进行。2. 蓝牙扫描与定位权限的强制关联一个容易被忽视但至关重要的点是从Android 12开始进行蓝牙设备扫描必须同时拥有ACCESS_FINE_LOCATION精确定位权限。这是Google出于隐私保护考虑做出的改变因为蓝牙扫描结果可能被用于推断用户的位置信息。2.1 权限组合策略在实际开发中我们需要根据不同的Android版本采取不同的权限组合策略Android版本必需权限6.0BLUETOOTH6.0-11BLUETOOTH ACCESS_COARSE_LOCATION12BLUETOOTH_SCAN ACCESS_FINE_LOCATION2.2 权限检查与请求的最佳实践为了避免权限缺失导致的崩溃建议在每次蓝牙操作前进行权限检查fun checkBluetoothPermissions(): Boolean { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { ContextCompat.checkSelfPermission( this, Manifest.permission.BLUETOOTH_SCAN ) PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) PackageManager.PERMISSION_GRANTED } else { ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) PackageManager.PERMISSION_GRANTED } }3. 虚拟MAC地址问题与解决方案从Android 10开始系统出于隐私保护考虑默认会返回虚拟MAC地址02:00:00:00:00:00而不是设备的真实物理地址。这一变化在Android 12中变得更加严格。3.1 为什么需要真实MAC地址虽然虚拟地址可以满足大多数蓝牙连接场景但在某些特殊情况下开发者仍然需要获取设备的真实MAC地址设备唯一标识与已有系统的兼容性需求特定厂商设备的特殊功能实现3.2 获取真实MAC地址的替代方案由于Android系统限制直接通过标准API获取真实MAC地址已经不可行。以下是几种可行的替代方案厂商特定API 许多设备厂商提供了自己的SDK来获取真实MAC地址。例如华为的DeviceInfoSDK// 华为设备获取真实MAC地址 String macAddress DeviceInfo.getMacAddress();低级别系统调用 对于root设备或系统应用可以通过读取/sys/class/net/wlan0/address等系统文件获取。配对时记录 在设备首次配对时记录MAC地址之后使用该记录值。提示使用厂商特定API时务必添加相应的权限声明并处理API不可用的情况。4. 跨版本兼容性实现在实际项目中我们经常需要支持多个Android版本。以下是实现蓝牙功能跨版本兼容的关键点4.1 权限声明兼容在AndroidManifest.xml中需要声明所有可能用到的权限uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN / uses-permission android:nameandroid.permission.BLUETOOTH_SCAN android:usesPermissionFlagsneverForLocation / uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / uses-permission android:nameandroid.permission.BLUETOOTH_ADVERTISE / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION /4.2 运行时兼容处理在代码中需要根据运行时的API级别动态调整行为fun startBluetoothScan() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { // Android 12 方式 if (checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) PackageManager.PERMISSION_GRANTED) { bluetoothLeScanner.startScan(scanCallback) } } else { // 旧版本方式 if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) PackageManager.PERMISSION_GRANTED) { bluetoothAdapter.startLeScan(leScanCallback) } } }4.3 厂商设备特殊处理对于鸿蒙等非标准Android系统可能需要额外的兼容性处理fun getBluetoothMacAddress(): String { return try { if (isHarmonyOS()) { // 鸿蒙设备特殊处理 getHarmonyMacAddress() } else if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { // Android 12 处理 getBluetoothAdapter().address ?: DEFAULT_MAC } else { // 旧版Android处理 getBluetoothAdapter().address } } catch (e: SecurityException) { DEFAULT_MAC } }5. 实战完整的蓝牙扫描实现下面是一个完整的Android 12蓝牙扫描实现示例包含了权限处理、版本兼容和错误处理class BluetoothScannerActivity : AppCompatActivity() { private lateinit var bluetoothLeScanner: BluetoothLeScanner private val scanCallback object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { super.onScanResult(callbackType, result) // 处理扫描结果 } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_bluetooth_scanner) val bluetoothManager getSystemService(BLUETOOTH_SERVICE) as BluetoothManager bluetoothLeScanner bluetoothManager.adapter.bluetoothLeScanner requestBluetoothPermissions() } private fun requestBluetoothPermissions() { val permissionsToRequest mutableListOfString() if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { if (checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) ! PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_SCAN) } } if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ! PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION) } if (permissionsToRequest.isNotEmpty()) { requestPermissions( permissionsToRequest.toTypedArray(), REQUEST_CODE_BLUETOOTH_PERMISSIONS ) } else { startBluetoothScan() } } private fun startBluetoothScan() { if (!checkBluetoothPermissions()) { Toast.makeText(this, 缺少必要的权限, Toast.LENGTH_SHORT).show() return } val scanSettings ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build() val scanFilters listOfScanFilter() // 可根据需要添加过滤条件 try { bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback) } catch (e: SecurityException) { Log.e(BluetoothScanner, 权限不足: ${e.message}) } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Arrayout String, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode REQUEST_CODE_BLUETOOTH_PERMISSIONS) { if (grantResults.all { it PackageManager.PERMISSION_GRANTED }) { startBluetoothScan() } else { Toast.makeText(this, 部分权限被拒绝, Toast.LENGTH_SHORT).show() } } } override fun onDestroy() { super.onDestroy() try { bluetoothLeScanner.stopScan(scanCallback) } catch (e: Exception) { Log.e(BluetoothScanner, 停止扫描失败: ${e.message}) } } }在鸿蒙3.0设备上测试时发现除了标准Android权限外还需要添加以下权限声明uses-permission android:namecom.huawei.permission.sec.MDM_BLUETOOTH /通过系统日志分析我们发现鸿蒙系统对蓝牙权限的检查更为严格特别是在企业设备管理场景下。这提示我们在开发跨平台应用时需要充分测试不同厂商设备的兼容性。