63、权限管理:运行时权限请求、权限分组、特殊权限(悬浮窗、通知)
权限管理,说白了就是你的App能不能碰用户手机里的敏感数据。我记得刚入行那会儿,Android 6.0还没普及,只要在清单文件里写个权限声明,App就能为所欲为。后来Google意识到这样不行——你一个手电筒App凭什么读通讯录?于是运行时权限机制就来了。
今天咱们就把权限管理这块彻底聊透。我会从最基础的运行时权限讲起,再到权限分组、特殊权限,最后聊聊我在实际项目中踩过的坑。
运行时权限:从清单声明到动态请求
先看一个最简单的例子。假设你的App需要读取联系人:
// 第一步:在AndroidManifest.xml中声明
<uses-permission android:name="android.permission.READ_CONTACTS" />
// 第二步:在代码中检查并请求
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_READ_CONTACTS);
}
嗯,这里要注意。很多新手会犯一个错误:只写了清单声明,没写运行时请求。结果在Android 6.0以上的设备上,权限永远拿不到。我有个朋友就因为这个,App上线后被用户骂惨了。
请求权限后,系统会弹出一个对话框。用户可以选择「允许」或「拒绝」。如果用户拒绝了,你需要在回调里处理:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_READ_CONTACTS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予,可以读取联系人
} else {
// 权限被拒绝
// 这里可以提示用户,或者引导去设置页面
}
}
}
权限分组:为什么有些权限不会弹窗?
你可能会发现,有些权限请求时系统会直接授予,根本不弹窗。这就是权限分组在起作用。
Android把权限分成了几个等级:
| 权限等级 | 特点 | 示例 |
|---|---|---|
| 普通权限(Normal) | 系统自动授予,不弹窗 | INTERNET、ACCESS_NETWORK_STATE |
| 危险权限(Dangerous) | 需要运行时请求,会弹窗 | READ_CONTACTS、CAMERA、ACCESS_FINE_LOCATION |
| 特殊权限(Special) | 需要跳转系统设置页面手动开启 | SYSTEM_ALERT_WINDOW、REQUEST_INSTALL_PACKAGES |
危险权限还按功能分成了组。比如「联系人」组包含READ_CONTACTS和WRITE_CONTACTS。如果你申请了READ_CONTACTS,系统会自动把同组的WRITE_CONTACTS也授予。反过来,如果用户拒绝了READ_CONTACTS,同组的其他权限也会被拒绝。
特殊权限:悬浮窗和通知
特殊权限是Android里最麻烦的一类。它们不能通过常规的requestPermissions来申请,必须跳转到系统设置页面,让用户手动开启。
悬浮窗权限
悬浮窗权限(SYSTEM_ALERT_WINDOW)常用于悬浮球、视频小窗等功能。申请方式如下:
// 检查悬浮窗权限
if (Settings.canDrawOverlays(this)) {
// 已有权限,可以显示悬浮窗
} else {
// 没有权限,跳转到设置页面
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_OVERLAY);
}
跳转后,用户需要在设置页面找到你的App,手动打开「显示在其他应用上层」的开关。这个过程很繁琐,用户很可能直接放弃。所以我一般会在跳转前弹一个说明对话框,告诉用户为什么要这个权限。
通知权限
从Android 13开始,通知权限也变成了运行时权限。这意味着你的App不能默认给用户发通知了,必须主动申请。
// 检查通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.POST_NOTIFICATIONS},
REQUEST_CODE_NOTIFICATIONS);
}
}
这里有个细节:如果用户拒绝了通知权限,你可以在合适的时机再次申请。但不要一上来就弹窗,那样很容易被用户永久拒绝。我一般会在用户完成某个关键操作后,再提示开启通知。
- 普通权限:清单声明即可,系统自动授予
- 危险权限:清单声明 + 运行时请求,需要处理用户拒绝
- 特殊权限:清单声明 + 跳转系统设置,需要引导用户手动开启
知识体系总览
下面这张图把权限管理的核心逻辑串起来了。你可以看到从权限声明到最终授予的完整流程:
避坑指南
做权限管理这么多年,我总结了几条血泪教训:
- 不要一次性申请所有权限。用户看到一堆弹窗,大概率会全部拒绝。按需申请,用到哪个就申请哪个。
- 处理「不再询问」的情况。如果用户勾选了「不再询问」,下次requestPermissions会直接返回拒绝。这时候你需要引导用户去设置页面手动开启。
- 特殊权限要提前说明。跳转系统设置前,最好弹一个对话框解释为什么需要这个权限。用户理解了,才愿意去手动开启。
- 兼容低版本。Android 6.0以下的设备不需要运行时权限,但你的代码要处理好版本判断,别在低版本上崩溃。
权限管理看似简单,但做不好真的很影响用户体验。你想想看,用户刚下载你的App,还没看到功能呢,先被一堆权限弹窗轰炸,换谁都会反感。所以我的原则是:能晚申请就晚申请,能不申请就不申请。实在需要,也要给用户一个合理的解释。
好了,关于权限管理就聊这么多。记住一句话:尊重用户的选择,才能赢得用户的信任。