25、权限与蓝牙:BLUETOOTH_SCAN、BLUETOOTH_CONNECT、蓝牙权限变化

蓝牙权限这块,说实话,是Android权限体系里变化最频繁的模块之一。我刚开始做蓝牙开发那会儿,还只有BLUETOOTHBLUETOOTH_ADMIN两个权限,后来Android 12一出来,直接拆成了BLUETOOTH_SCANBLUETOOTH_CONNECTBLUETOOTH_ADVERTISE。嗯,这里面的坑,我踩过不少。

为什么要把一个权限拆成三个?

说白了,就是权限粒度太粗了。你想想看,一个地图App要扫描蓝牙设备,它其实只需要扫描能力,根本不需要连接设备。但老的权限模型里,你申请了BLUETOOTH,就等于拿到了所有蓝牙操作的通行证。这显然不合理。

Android 12之后,Google把蓝牙权限拆成了三个独立的能力:

  • BLUETOOTH_SCAN:只负责扫描周边蓝牙设备
  • BLUETOOTH_CONNECT:只负责连接已发现的设备
  • BLUETOOTH_ADVERTISE:只负责广播自身(这个用得少)

我个人习惯,在项目里会按最小权限原则来申请。比如一个信标扫描库,我只会申请BLUETOOTH_SCAN,绝不碰BLUETOOTH_CONNECT

运行时权限的变化要点

这里有个关键变化——位置权限的剥离。在Android 12之前,扫描蓝牙设备必须同时拥有位置权限。但从Android 12开始,如果你只做蓝牙扫描,不再需要位置权限了。

为什么会这样?因为Google引入了BLUETOOTH_SCAN这个新权限,它本身就包含了扫描的合法性。但注意,这有个前提——你的App不能通过蓝牙扫描来推断用户位置。

⚠️ 重要警告: 如果你的App确实需要通过蓝牙扫描来获取位置信息(比如室内定位),那么你仍然需要申请ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION权限。别以为有了BLUETOOTH_SCAN就能绕过位置权限,Google Play审核会直接拒绝你的App。

代码实战:如何正确申请蓝牙权限

嗯,这里我直接给出一份我在项目中实际使用的权限申请代码。注意看Android版本的判断逻辑。

// 判断是否需要位置权限
private boolean needsLocationPermission() {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.S;
}

// 构建权限列表
private String[] getBluetoothPermissions() {
    List<String> permissions = new ArrayList<>();
    
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        // Android 12+:使用新权限
        permissions.add(Manifest.permission.BLUETOOTH_SCAN);
        permissions.add(Manifest.permission.BLUETOOTH_CONNECT);
    } else {
        // Android 11及以下:使用旧权限
        permissions.add(Manifest.permission.BLUETOOTH);
        permissions.add(Manifest.permission.BLUETOOTH_ADMIN);
    }
    
    // 如果需要位置信息,添加位置权限
    if (needsLocationPermission()) {
        permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
    }
    
    return permissions.toArray(new String[0]);
}

// 检查权限
private boolean hasBluetoothPermissions() {
    for (String permission : getBluetoothPermissions()) {
        if (ContextCompat.checkSelfPermission(this, permission) 
            != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}
💡 我的经验: 我曾经在一个项目中,因为没处理好Android 12以下的兼容性,导致用户在旧设备上永远拿不到蓝牙权限。后来我加了个版本判断,把旧权限和新权限分开处理,问题就解决了。记住,BLUETOOTH_SCAN只在API 31+有效。

蓝牙权限的声明方式

除了运行时申请,你还需要在AndroidManifest.xml里声明权限。这里有个细节——BLUETOOTH_SCANBLUETOOTH_CONNECT运行时权限,但BLUETOOTH_ADVERTISE普通权限,不需要运行时申请。

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

<!-- Android 12+ 新权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- 如果需要位置信息 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" 
    android:maxSdkVersion="30" />

注意我加了android:maxSdkVersion="30",这表示位置权限只在Android 11及以下需要。Android 12+不需要位置权限了,这个属性可以帮我们自动屏蔽掉。

权限变化的时间线

我把蓝牙权限的变化整理成了一张表,方便你对照:

Android版本 所需权限 是否需要位置权限 备注
Android 6-11 BLUETOOTH + BLUETOOTH_ADMIN 是(扫描时需要) 旧权限模型
Android 12+ BLUETOOTH_SCAN + BLUETOOTH_CONNECT 否(仅扫描时) 新权限模型
Android 12+(定位场景) BLUETOOTH_SCAN + BLUETOOTH_CONNECT + 位置权限 需要推断位置时

避坑指南:我曾经踩过的三个坑

第一个坑:忘记处理权限被拒绝的情况

我曾经在一个蓝牙门禁项目里,用户拒绝了BLUETOOTH_SCAN权限,结果App直接崩溃了。后来我加了个权限回调处理,如果用户拒绝,就弹出一个友好的提示,引导用户去设置里开启。

第二个坑:混淆了扫描和连接

有个同事在代码里只申请了BLUETOOTH_SCAN,但后面调用了connectGatt()方法。结果在Android 12设备上,连接永远失败。记住,扫描和连接是两个独立的权限,缺一不可。

第三个坑:位置权限的过度申请

我见过不少App,明明只是做个蓝牙耳机配对,却申请了ACCESS_FINE_LOCATION。这在Android 12+上完全没必要,而且会让用户觉得你的App在偷窥位置。我的建议是:只申请你真正需要的权限

知识体系总览

下面这张图,我把蓝牙权限的核心逻辑画出来了。你可以看到,从Android 12开始,权限的申请路径分成了两条:一条是纯蓝牙操作,另一条是涉及位置信息的蓝牙操作。

蓝牙权限申请决策流程 开始蓝牙操作 Android版本 ≥ Android 12 ≤ Android 11 BLUETOOTH_SCAN BLUETOOTH_CONNECT 需要位置? +位置权限 BLUETOOTH BLUETOOTH_ADMIN 必须+位置权限 权限检查完成

从这张图可以看得很清楚:Android 12以下,蓝牙权限和位置权限是绑定的Android 12以上,两者解耦了,但如果你要用蓝牙做定位,还是得申请位置权限。

📌 核心要点:
  • Android 12+ 使用 BLUETOOTH_SCAN + BLUETOOTH_CONNECT
  • Android 11- 使用 BLUETOOTH + BLUETOOTH_ADMIN + 位置权限
  • 纯蓝牙扫描不需要位置权限(Android 12+)
  • 蓝牙定位场景仍然需要位置权限
  • 权限声明要加 android:maxSdkVersion 做版本控制

好了,蓝牙权限这块的内容就这些。记住,权限管理不是写一次就完事的,每次Android版本更新,你都得回头看看自己的权限逻辑是否还适用。保持更新,保持警惕。


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