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 原理与调用流程

运行时权限检查是“查询→判断→申请→回调”链路的起点。典型流程如下:

  1. 权限查询:使用 checkSelfPermission() 判断当前权限是否已授予。
  2. 理由判断:若未授予,调用 shouldShowRequestPermissionRationale() 决定是否先展示理由。
  3. 发起申请:根据判断结果,或展示理由后调用 requestPermissions(),或直接调用 requestPermissions()。
  4. 回调处理:在 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调用点,红色节点为分支决策,绿色节点为最终操作。

checkSelfPermission 与 shouldShowRequestPermissionRationale 流程序列 权限检查与决策流程图 checkSelfPermission() 已授权? 执行功能操作 shouldShowRequestPermissionRationale() 需要理由? 展示解释UI requestPermissions() 注:应在 Activity/Fragment 中调用,且必须等待用户响应后再处理回调结果

3.8 最佳实践总结

  • 始终先调用 checkSelfPermission,再决定下一步逻辑。
  • 在 shouldShowRequestPermissionRationale 返回 true 时提供简洁、直接的UI解释,不要强制用户阅读长篇说明。
  • 权限被永久拒绝后,避免无意义地重复申请;引导用户前往系统设置手动授权。
  • 将权限检查封装为一个工具方法,避免分散在各处重复代码。
核心记忆点:checkSelfPermission 回答“现在能不能用?”,shouldShowRequestPermissionRationale 回答“要不要先解释?”——两者共同构成了权限申请前的智能判断层,是用户体验优化的关键。