3、运行时请求:ActivityCompat.requestPermissions、权限回调处理、用户授权流程

好,咱们接着聊运行时权限。上一章我们把权限声明和检查讲透了,这一章进入真正的“临门一脚”——怎么向用户发起请求,怎么处理用户的回应。

说实话,这个环节是很多开发者翻车的地方。我自己刚接触动态权限时,也踩过不少坑。比如有一次,我天真地以为用户点了“允许”就万事大吉,结果在回调里忘了判断权限是否真的授予了——嗯,后来被测试小姐姐追着改bug,记忆犹新。

3.1 发起请求:ActivityCompat.requestPermissions

检查完权限,发现没授权,那就得请求了。Android提供了标准API:

// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    // 没授权,发起请求
    ActivityCompat.requestPermissions(
        this,
        new String[]{Manifest.permission.CAMERA},
        REQUEST_CODE_CAMERA
    );
} else {
    // 已有权限,直接干活
    openCamera();
}

这里有几个关键点,我挨个说一下:

  • 第一个参数是Activity或Fragment:如果你在Fragment里调用,建议用Fragment自己的requestPermissions方法,而不是ActivityCompat。否则回调会走到Activity里,处理起来麻烦。
  • 第二个参数是权限数组:可以一次请求多个权限。但我个人习惯一次只请求一个,用户体验更好。你想想看,用户突然看到一堆弹窗,大概率会烦躁。
  • 第三个参数是请求码:随便定义一个int常量,只要在回调里能区分是哪个请求就行。我一般用0x10、0x20这样的十六进制,看着清晰。
💡 我的小技巧: 请求码不要用0、1、2这种太简单的数字,万一和其他请求码冲突了,调试起来很头疼。我习惯用权限名的hashCode取低16位,基本不会重复。

3.2 权限回调处理:onRequestPermissionsResult

用户看到弹窗后,会点“允许”或“拒绝”。结果会通过onRequestPermissionsResult回调告诉你。这是整个流程的核心,写不好就出大问题。

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    if (requestCode == REQUEST_CODE_CAMERA) {
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 用户同意了,开干
            openCamera();
        } else {
            // 用户拒绝了
            showPermissionDeniedDialog();
        }
    }
}

这段代码看起来简单,但有几个坑:

  • grantResults可能为空:如果用户取消了弹窗(比如按了返回键),grantResults数组长度就是0。所以一定要先判空再取值。
  • 权限和结果一一对应:permissions数组和grantResults数组的顺序是一致的。permissions[0]对应grantResults[0],以此类推。
  • 别忘了调用super:虽然不调也不一定会崩,但某些系统行为可能受影响。我建议养成好习惯,先调super。
⚠️ 我曾经踩过的坑: 有一次我在Fragment里请求权限,但回调写在了Activity里。结果用户点了允许,Fragment那边完全没反应。后来才发现,Fragment的requestPermissions会回调到Fragment自己的onRequestPermissionsResult,而不是Activity的。所以,谁发起请求,谁处理回调

3.3 用户授权流程:从弹窗到决策

整个授权流程,说白了就是用户和系统的一次对话。我画了一张图,帮你理清逻辑:

用户授权流程图 检查权限状态 已授权? 直接执行操作 调用 requestPermissions 系统弹出权限对话框 用户决策

用户看到弹窗后,通常有三种反应:

用户操作 grantResults值 后续处理
点击“允许” PERMISSION_GRANTED 直接执行需要权限的功能
点击“拒绝” PERMISSION_DENIED 提示用户功能受限,不要反复弹窗
点击“拒绝并不再询问” PERMISSION_DENIED 引导用户去设置页面手动开启

重点来了: 用户拒绝后,下次再请求时,系统可能不再弹窗。这时候你需要用 shouldShowRequestPermissionRationale 来判断。如果返回false,说明用户选了“不再询问”,你就得引导他去设置里手动开启。

// 判断是否需要显示解释说明
if (shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
    // 用户拒绝过但没选“不再询问”,可以再解释一下
    showRationaleDialog();
} else {
    // 用户选了“不再询问”,直接引导去设置
    goToAppSettings();
}

我个人习惯在用户第一次拒绝时,弹一个友好的提示框,解释为什么需要这个权限。如果用户再次拒绝,就不再打扰了,直接引导去设置。这样既尊重用户,又不至于让用户觉得烦。

💡 避坑指南: 我曾经在一个项目里,用户拒绝后直接再次请求权限,结果被系统判定为“骚扰”,弹窗都不显示了。后来我加了一个判断:如果用户拒绝超过一次,就不再自动请求,而是显示一个“去设置开启”的按钮。用户体验好了很多。

好了,运行时请求的核心内容就这些。记住三个关键点:发起请求用requestPermissions,处理结果在onRequestPermissionsResult,用户拒绝后要合理引导。下一章我们会聊更高级的话题——权限分组和特殊权限的处理,到时候见。


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