2. Android蓝牙权限适配:从Android 6到14的演变之路
蓝牙权限适配,说实话是很多开发者容易踩坑的地方。我记得刚接手一个蓝牙项目时,用户反馈说"扫描不到设备",结果排查了半天——原来是权限没申请对。嗯,今天我们就来彻底捋清楚这件事。
2.1 权限模型的演变:从粗放到精细
Android的蓝牙权限,经历了三个阶段的变化。我把它总结成一张图,方便你理解:
你看,从Android 12开始,Google终于把蓝牙权限从位置权限中剥离出来了。我个人觉得这是最合理的做法——蓝牙就是蓝牙,跟位置有什么关系呢?
2.2 三个新权限详解
Android 12引入了三个新权限,咱们一个一个说清楚。
2.2.1 BLUETOOTH_SCAN
这个权限用来扫描附近的蓝牙设备。说白了,就是调用startScan()时需要的。以前你得同时申请位置权限,现在不用了。
核心要点:
- 只用于扫描,不用于连接
- 不需要位置权限(Android 12+)
- 属于
never for location类型
2.2.2 BLUETOOTH_CONNECT
连接设备、配对、传输数据时用这个权限。我遇到过一个问题:有个同事只申请了SCAN权限,结果连接时直接崩溃——嗯,SecurityException没跑。
注意:如果你同时需要扫描和连接,两个权限都得申请。别想着偷懒只申请一个。
2.2.3 BLUETOOTH_ADVERTISE
这个权限是给外围设备用的。如果你的App要作为蓝牙外设广播数据,就需要它。比如做蓝牙信标、或者自定义广播包。
我个人习惯把这三个权限的关系理解成:
- SCAN = 你看别人
- CONNECT = 你跟别人握手
- ADVERTISE = 你让别人看你
2.3 动态权限申请实战
好了,理论说完了,咱们直接上代码。这是我项目中实际在用的方案。
2.3.1 声明权限
首先在AndroidManifest.xml里声明:
<!-- 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" />
<!-- Android 11及以下兼容 -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- 位置权限(Android 11及以下扫描需要) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
小技巧:用maxSdkVersion="30"限制旧权限,这样在Android 12+上就不会提示用户授权旧权限了。我刚开始没加这个,结果用户说"怎么要这么多权限"...
2.3.2 动态申请代码
这里我封装了一个工具方法,直接复制就能用:
public class BluetoothPermissionHelper {
private static final int REQUEST_CODE_BLUETOOTH = 1001;
/**
* 获取当前Android版本需要的蓝牙权限列表
*/
public static String[] getRequiredPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+
return new String[]{
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_ADVERTISE
};
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10-11
return new String[]{
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION
};
} else {
// Android 6-9
return new String[]{
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION
};
}
}
/**
* 检查并申请权限
*/
public static void checkAndRequestPermissions(Activity activity) {
String[] permissions = getRequiredPermissions();
List<String> needRequest = new ArrayList<>();
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(activity, perm)
!= PackageManager.PERMISSION_GRANTED) {
needRequest.add(perm);
}
}
if (!needRequest.isEmpty()) {
ActivityCompat.requestPermissions(
activity,
needRequest.toArray(new String[0]),
REQUEST_CODE_BLUETOOTH
);
} else {
// 权限已全部授予,可以开始蓝牙操作
startBluetoothWork();
}
}
/**
* 在Activity的onRequestPermissionsResult中调用
*/
public static void handlePermissionResult(int requestCode,
String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_CODE_BLUETOOTH) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
startBluetoothWork();
} else {
// 提示用户权限不足
showPermissionDeniedDialog();
}
}
}
}
2.3.3 权限适配对照表
为了方便查阅,我整理了一张对照表:
| 功能场景 | Android 6-9 | Android 10-11 | Android 12+ |
|---|---|---|---|
| 扫描设备 | BLUETOOTH + 位置 | BLUETOOTH + 位置 | BLUETOOTH_SCAN |
| 连接设备 | BLUETOOTH | BLUETOOTH | BLUETOOTH_CONNECT |
| 广播数据 | BLUETOOTH_ADMIN | BLUETOOTH_ADMIN | BLUETOOTH_ADVERTISE |
| 后台扫描 | 位置 + 后台位置 | 位置 + 后台位置 | BLUETOOTH_SCAN |
2.4 避坑指南
做蓝牙开发这些年,我踩过的坑不少。分享几个典型的:
坑1:Android 12上忘记适配新权限
我曾经有个App在Android 11上跑得好好的,升级到Android 12后直接闪退。原因就是没加BLUETOOTH_SCAN和BLUETOOTH_CONNECT。记住:Android 12上旧的BLUETOOTH权限已经不管用了。
坑2:位置权限的兼容处理
如果你要兼容Android 11及以下设备,位置权限还是得申请。我建议用maxSdkVersion来控制,别一股脑全申请。
我的建议:在Application的onCreate里判断SDK版本,然后动态决定申请哪些权限。这样代码更干净,用户也少看到弹窗。
2.5 总结
蓝牙权限适配,说白了就是两件事:
- 搞清楚你的App需要哪些蓝牙功能(扫描?连接?广播?)
- 根据Android版本选择合适的权限组合
我个人习惯在项目初期就把权限适配框架搭好,后面开发就省心了。你想想看,如果等到上线才发现权限问题,那可就麻烦了。
好了,这一章就到这里。记住:权限适配不是难事,但漏掉一个权限,用户可能就直接用不了你的App了。
公众号:蓝海资料掘金营,微信deep3321