3. 运行时权限检查API详解:checkSelfPermission、shouldShowRequestPermissionRationale
运行时权限检查是Android权限申请链路的第一道关卡。系统提供了两个核心API:checkSelfPermission() 用于查询当前权限授予状态,shouldShowRequestPermissionRationale() 用于判断是否需要向用户展示申请理由。本章深入剖析这两个API的原理、调用时机、返回值含义以及常见误用场景。
3.1 API 原型与返回值
两个API均属于 ContextCompat 和 ActivityCompat 工具类(支持库版本),也可直接调用 Context.checkSelfPermission() 和 Activity.shouldShowRequestPermissionRationale()。它们的返回类型和语义如下:
| API | 方法签名 | 返回值 | 含义 |
|---|---|---|---|
checkSelfPermission |
int checkSelfPermission(String permission) |
PackageManager.PERMISSION_GRANTED (0)或 PackageManager.PERMISSION_DENIED (-1) |
查询当前进程是否被授予指定权限 |
shouldShowRequestPermissionRationale |
boolean shouldShowRequestPermissionRationale(String permission) |
true 或 false |
判断是否需要显示申请理由(弹窗或UI提示) |
重要说明:
shouldShowRequestPermissionRationale() 的返回值依赖于用户历史操作:当用户首次申请权限且从未拒绝时返回 false;当用户曾拒绝过一次但未勾选“不再询问”时返回 true;当用户拒绝并勾选了“不再询问”或设备策略永久禁止时返回 false。
3.2 原理与调用流程
运行时权限检查是“查询→判断→申请→回调”链路的起点。典型流程如下:
- 权限查询:使用
checkSelfPermission()判断当前权限是否已授予。 - 理由判断:若未授予,调用
shouldShowRequestPermissionRationale()决定是否先展示理由。 - 发起申请:根据判断结果,或展示理由后调用
requestPermissions(),或直接调用requestPermissions()。 - 回调处理:在
onRequestPermissionsResult()中再次检查授予状态。
两个API之间是串联关系,错误的使用顺序会导致用户体验下降或申请失败。
常见陷阱:不要在
onCreate() 或 onResume() 中直接调用 requestPermissions() 而不做检查。必须先检查权限,否则导致重复申请或权限请求被系统忽略。
3.3 代码示例与解析
以下是一个标准的使用片段(适用于 Activity 或 Fragment):
// 检查存储权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 权限未授予,判断是否需要展示理由
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
// 向用户解释为什么需要此权限
showRationaleDialog();
} else {
// 直接请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_CODE_STORAGE);
}
} else {
// 权限已授予,执行相关操作
readFileFromExternalStorage();
}
// 权限请求回调
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_STORAGE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readFileFromExternalStorage();
} else {
// 权限被拒绝,可再次检查是否应展示 rationale
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
// 用户勾选了“不再询问”,引导去设置
showSettingsDialog();
}
}
}
}
3.4 调试与验证
调试时可通过 adb shell dumpsys package 查看当前应用的权限授予状态:
adb shell dumpsys package [你的包名] | grep -A 5 "runtime permissions"
也可以手动修改模拟器中应用权限(设置→应用→权限),验证 checkSelfPermission 返回值的正确性。shouldShowRequestPermissionRationale 的调试需要模拟用户拒绝操作:
- 首次拒绝(不勾选“不再询问”)→ 应返回
true - 再次拒绝并勾选“不再询问”→ 应返回
false - 从未请求过该权限 → 应返回
false
3.5 常见错误与规避
| 错误场景 | 表现 | 正确做法 |
|---|---|---|
忽略 checkSelfPermission 直接申请 |
权限重复请求,用户反感 | 强制先检查,再决定是否申请 |
在 shouldShowRequestPermissionRationale 返回 false 时仍展示解释 |
用户看到无意义的弹窗 | 严格按照返回值控制是否展示理由 |
权限回调中再次调用 requestPermissions |
死循环申请 | 根据授予结果做决策,非必要不重复申请 |
忘记在 onRequestPermissionsResult 中处理用户永久拒绝 |
功能不可用且无引导 | 判断 shouldShow 后跳转系统设置 |
3.6 性能与稳定性考量
这两个API本身是轻量级IPC调用,不会引起性能问题。但需注意:
- 不要在主线程频繁调用:在一次权限检查流程中,最多调用两次(check + shouldShow),不会超过UI线程响应阈值。
- 避免在循环或列表绑定中重复检查:缓存权限状态(例如存入成员变量),只在权限变化时重新检查。
- 兼容性处理:Android 6.0以下系统这两个API不存在,建议用
Build.VERSION.SDK_INT进行版本判断,低于23时默认授予或使用旧权限模型。
// 版本兼容示例
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 使用 checkSelfPermission 和 shouldShowRequestPermissionRationale
} else {
// 直接执行权限相关操作(自动安装时授权)
}
3.7 核心流程示意图
下图展示了从权限查询到申请决策的完整检查链路,蓝色节点为API调用点,红色节点为分支决策,绿色节点为最终操作。
3.8 最佳实践总结
- 始终先调用
checkSelfPermission,再决定下一步逻辑。 - 在
shouldShowRequestPermissionRationale返回true时提供简洁、直接的UI解释,不要强制用户阅读长篇说明。 - 权限被永久拒绝后,避免无意义地重复申请;引导用户前往系统设置手动授权。
- 将权限检查封装为一个工具方法,避免分散在各处重复代码。
核心记忆点:
checkSelfPermission 回答“现在能不能用?”,shouldShowRequestPermissionRationale 回答“要不要先解释?”——两者共同构成了权限申请前的智能判断层,是用户体验优化的关键。