1. Android权限机制概述:分类、危险权限与申请流程

做Android测试这么多年,我越来越觉得权限这块是回归测试里最容易翻车的地方。你想想看,一个权限弹窗没处理好,轻则功能闪退,重则直接被用户投诉到应用商店下架。所以咱们开篇第一件事,得把Android权限这套机制彻底理清楚。

我个人习惯把权限问题分成两类来理解:底层机制怎么设计的,和代码里怎么处理的。理解透了,后面做Mock测试才能有的放矢。

1.1 权限的本质与分类

权限说白了就是一道安全门。应用想访问用户的相机、通讯录、位置这些敏感资源,必须经过用户同意。Android系统把权限分成了几大类,咱们直接看表:

权限类型 申请时机 用户可拒绝 示例
普通权限 (Normal) 安装时自动授予 否 INTERNET, ACCESS_NETWORK_STATE
危险权限 (Dangerous) 运行时动态申请 是 CAMERA, READ_CONTACTS, ACCESS_FINE_LOCATION
特殊权限 (Special) 系统设置中手动开启 是 SYSTEM_ALERT_WINDOW, WRITE_SETTINGS
签名权限 (Signature) 安装时自动匹配签名 否 同签名的系统级API

实际项目中,我们99%的精力都在处理危险权限。为什么?因为它需要运行时弹窗,用户还可能点「拒绝」甚至「不再询问」。我在项目中遇到过用户连续拒绝两次后,权限弹窗再也不出来了,但代码里还在傻等回调——直接就崩了。

1.2 危险权限与普通权限的界限

其实从Android 6.0开始,权限这块的玩法就变了。以前安装时一次性全授权,用户要么全给要么不装。现在不一样了,用户可以随时在设置里关掉某个权限。

这一点在做回归测试时特别要命。我举个例子:

  • 普通权限:像访问网络这种,基本不用操心。系统安装时自动给,用户也关不掉(除非通过adb或root)。
  • 危险权限:像读取位置、读取联系人这种,每次调用前都必须检查授权状态。

而且危险权限还分权限组。比如你申请了CAMERA,如果用户同意了,同组的其他权限(如果有的话)也会被自动授予。但注意,Android 10之后这个行为有变化,后面章节会细说。

核心要点:回归测试中,危险权限的「拒绝后再次请求」场景是最容易漏测的。很多开发只测了「同意」路径,忘了测「拒绝」和「不再询问」路径。

1.3 权限申请的标准流程

现在大多数项目都走运行时权限申请。流程大概是这样:

  1. 清单声明:在AndroidManifest.xml里写上需要的权限。
  2. 运行时检查:调用ContextCompat.checkSelfPermission()判断是否已授权。
  3. 如果未授权:调用ActivityCompat.requestPermissions()发起弹窗。
  4. 回调处理:在onRequestPermissionsResult()里根据用户选择做不同逻辑。
  5. 特殊处理:如果用户勾选了「不再询问」,需要引导用户去系统设置手动开启。

用代码表示就是下面这样。嗯,这段代码是开发里最常见的模板:

// 第一步:检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    // 第二步:是否需要展示rationale?
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, 
            Manifest.permission.CAMERA)) {
        // 用户之前拒绝过,但没选「不再询问」
        // 这里可以解释为什么需要这个权限
        showRationaleDialog();
    } else {
        // 正常请求
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.CAMERA},
                REQUEST_CAMERA_CODE);
    }
} else {
    // 已经授权,直接执行
    openCamera();
}

// 第四步:处理回调
@Override
public void onRequestPermissionsResult(int requestCode,
        @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_CAMERA_CODE) {
        if (grantResults.length > 0 
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            openCamera();
        } else {
            // 用户拒绝了
            showPermissionDeniedMessage();
        }
    }
}

避坑指南:我曾经有一个线上问题,就是因为在requestPermissions之前没有检查shouldShowRequestPermissionRationale,结果用户第二次拒绝后弹窗不再出现,但代码还在等回调,直接导致界面卡死。这个坑特别隐蔽,回归测试里一定要覆盖到。

1.4 权限申请的核心流程图

整个流程用一张图来总结,你一看就明白了:

Android 危险权限申请核心流程 应用启动 / 功能触发 检查权限是否已授权 已授权 → 直接执行功能 未授权 → 进入申请流程 是否需要展示 Rationale? 展示解释对话框 弹出系统授权对话框 用户同意 / 拒绝 回调处理授权结果 关键:拒绝后若勾选「不再询问」,需引导用户去系统设置手动开启

测试小贴士:做回归测试时,我建议你重点关注图中「未授权」→「展示Rationale」→「用户拒绝」这条路径。很多App的崩溃都发生在这里,因为开发只写了授权成功的回调,没写拒绝后的兜底逻辑。

1.5 开发中的实战建议

讲完流程,我再补充几个实际项目中的经验:

  • 不要依赖「一次性检查」:权限状态随时可能被用户在系统设置里改掉。每次使用敏感功能前都必须检查。
  • Mock测试要覆盖边界:我记得做相机功能回归时,Mock了「已授权」「拒绝一次」「拒绝并不再询问」「权限被系统撤销」四种状态,才算是完整覆盖。
  • 设备兼容性:不同厂商的Rom(比如MIUI、EMUI)对权限弹窗的处理方式都不一样。有些会拦截系统的dialog,换成自家的UI——这一点在Mock测试里特别容易被忽略。
  • Android 11的权限重置:如果用户长时间不用某个App,系统会自动重置它的运行时权限。这个行为在回归测试里一定要加上对应的用例。

一句话总结:Android权限测试的核心,不在于权限本身能不能拿到,而在于拿不到的时候代码能不能体面地退场。