16. Android 12+ BLE适配:蓝牙权限变更、精确位置要求、扫描过滤限制、兼容性处理

Android 12 这一版,说实话,对 BLE 开发者来说是个分水岭。我当年第一次在 Android 12 设备上跑老项目时,直接闪退加扫描不到设备,搞得我一头雾水。后来翻文档才发现,Google 把蓝牙权限体系整个重构了一遍。

这一章,我就把我在适配过程中踩过的坑、总结的经验,一次性讲清楚。

16.1 蓝牙权限的「三权分立」

Android 12 之前,你只需要 BLUETOOTHBLUETOOTH_ADMIN 两个权限就能跑 BLE。但 12 之后,Google 引入了三个新运行时权限:

权限名 用途 是否需要运行时申请
BLUETOOTH_SCAN 扫描 BLE 设备
BLUETOOTH_CONNECT 连接已扫描到的设备
BLUETOOTH_ADVERTISE 广播 BLE 信号(外设模式)

嗯,这里要注意:这三个权限是独立的。你只申请 SCAN 权限,是没法调用 connectGatt() 的。我见过不少同事只申请了 SCAN,结果连接时直接抛 SecurityException。

核心原则:扫描用 SCAN,连接用 CONNECT,广播用 ADVERTISE。别偷懒,三个都申请最稳妥。

16.2 精确位置要求——终于松绑了

Android 12 之前,扫描 BLE 必须申请 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION。这其实是个历史遗留问题——因为 BLE 扫描可以用来做粗略定位。

但从 Android 12 开始,如果你不需要基于 BLE 做定位,可以不申请位置权限。怎么告诉系统?在清单文件里加一行:

<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />

加上 neverForLocation 标志后,系统就知道你的扫描不用于定位,也就不会弹位置权限的对话框了。

警告:如果你在代码里调用了 startScan() 但没有声明 neverForLocation,系统仍然会要求位置权限。而且 Google Play 审核时会检查你的应用是否真的需要位置信息——别乱加这个标志。

我个人习惯是:如果我的 App 只是连接已知设备(比如智能手表),我就加 neverForLocation。如果 App 需要根据设备位置做推荐,那就不加,老老实实申请位置权限。

16.3 扫描过滤限制——Google 的「保护」有点过头

Android 12 引入了一个新限制:后台扫描会被限频。具体来说,如果你的 App 在后台(没有前台 Service 或 Activity),每 30 分钟只能扫描 4 次。每次扫描最长 30 秒。

为什么会这样?Google 说这是为了省电和保护隐私。但说实话,这对很多 IoT 应用来说简直是灾难。我曾经做过一个仓库资产追踪 App,需要在后台持续扫描 BLE 标签。Android 12 一上,扫描直接废了。

解决办法有两个:

  • 方案一:启动一个前台 Service,并声明 BLUETOOTH_SCAN 权限对应的通知。这样系统会认为你的扫描是「前台行为」,不受限频影响。
  • 方案二:使用 BluetoothLeScannerstartScan() 时,传入 ScanSettings 并设置 SCAN_MODE_LOW_LATENCY。但这只能提高扫描频率,不能突破后台限制。
// 前台 Service 示例
val scanSettings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
    .build()

// 在 onStartCommand 中启动扫描
bluetoothLeScanner.startScan(filters, scanSettings, scanCallback)

小技巧:如果你只是做「扫描-连接-断开」这种一次性操作,可以考虑用 BluetoothAdapterstartDiscovery() 替代。但注意,这个方法返回的是经典蓝牙设备,不是 BLE。

16.4 兼容性处理——让老设备也能跑

你的 App 肯定要兼容 Android 11 及以下的设备。这时候就需要做版本判断。我一般这样写:

// 在 Application 或 Activity 中
private fun requestBluetoothPermissions() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        // Android 12+:申请新权限
        requestPermissions(
            arrayOf(
                Manifest.permission.BLUETOOTH_SCAN,
                Manifest.permission.BLUETOOTH_CONNECT,
                Manifest.permission.BLUETOOTH_ADVERTISE
            ),
            REQUEST_CODE_BLE_PERMISSIONS
        )
    } else {
        // Android 11 及以下:申请位置权限
        requestPermissions(
            arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION
            ),
            REQUEST_CODE_LOCATION_PERMISSIONS
        )
    }
}

另外,清单文件里也要做兼容声明:

<!-- Android 12+ 权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation"
    android:maxSdkVersion="33" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"
    android:maxSdkVersion="33" />

<!-- Android 11 及以下 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
    android:maxSdkVersion="30" />

<!-- 基础权限(所有版本都需要) -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

注意 maxSdkVersion 的用法。这样在 Android 12+ 设备上,系统不会去检查位置权限,避免不必要的弹窗。

16.5 知识体系总览

下面这张图是我自己整理的 Android 12+ BLE 适配核心逻辑,你可以对照着看:

Android 12+ BLE 适配核心逻辑 Android 12+ BLE 适配 权限三权分立 SCAN / CONNECT / ADVERTISE 精确位置松绑 neverForLocation 标志 后台扫描限频 前台 Service 突破限制 兼容性处理:版本判断 + maxSdkVersion 核心思路:Android 12+ 用新权限体系,Android 11- 用旧权限体系 通过 Build.VERSION.SDK_INT 做分支,清单文件用 maxSdkVersion 隔离

16.6 避坑指南

最后,分享几个我实际项目中踩过的坑:

  • 权限申请顺序:先申请 SCAN,再申请 CONNECT。如果反过来,系统可能会认为你的 App 行为异常。我遇到过用户反馈「点了允许但连接失败」,就是因为顺序反了。
  • 模拟器测试:Android 12 模拟器对 BLE 支持很差。我建议直接用真机测试。曾经在模拟器上跑了一整天,所有权限都正常,结果真机上一运行就崩——因为模拟器没有真正的蓝牙硬件。
  • targetSdkVersion:如果你的 targetSdkVersion 低于 31,系统会以兼容模式运行,不会强制要求新权限。但 Google Play 从 2022 年开始要求新应用必须 target 31+。所以别想着靠低版本绕过去。
  • 权限撤销:Android 12 允许用户随时撤销运行时权限。你的 App 要能处理权限被撤销的情况——比如在 onResume 里检查权限状态,如果被撤销就重新申请。

个人建议:写一个统一的权限管理类,把 Android 12+ 和 Android 11- 的逻辑封装好。这样以后升级到 Android 14、15 时,只需要改这个类就行。我自己的项目就是这么做的,省了不少事。

好了,Android 12+ 的 BLE 适配就讲到这里。核心就是三件事:权限分家、位置松绑、后台限频。把这三点处理好,你的 App 就能在 Android 12 到 14 上平稳运行。


公众号:蓝海资料掘金营,微信deep3321