3、Android权限模型详解:普通权限、危险权限、签名权限、特权权限的定义与区别,权限申请流程与运行时权限机制

说到Android权限,我最早接触的时候也觉得挺绕的。特别是从Android 6.0开始,权限模型经历了一次大手术。今天咱们就把这四种权限掰开揉碎了讲清楚。

3.1 权限的四个等级:从松到严

Android把权限分成了四个等级。说白了,就是根据权限的「危险程度」来分级管理。我习惯把它们想象成四道门,每道门的锁不一样。

权限等级 定义 安装时行为 运行时行为 典型例子
普通权限 (Normal) 不会直接威胁用户隐私或设备安全 自动授予 无需用户干预 INTERNET、ACCESS_NETWORK_STATE
危险权限 (Dangerous) 可能访问用户敏感数据或控制设备 不自动授予 需要用户弹窗确认 CAMERA、READ_CONTACTS
签名权限 (Signature) 仅允许相同签名的应用使用 自动授予(同签名) 无需用户干预 系统级内部接口
特权权限 (Privileged) 系统预置应用才能使用 自动授予(预置应用) 无需用户干预 WRITE_SETTINGS、INSTALL_PACKAGES

嗯,这里要注意:普通权限和危险权限是应用开发者最常打交道的。签名权限和特权权限,说实话,普通应用基本碰不到。

3.2 普通权限:最省心的那类

普通权限,说白了就是那些「就算给了你,也干不了什么坏事」的权限。比如访问网络、设置闹钟、使用蓝牙这些。

在AndroidManifest.xml里声明一下就行:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SET_ALARM" />

系统会在安装时自动授予,用户甚至不会感知到。我在项目中遇到过一个问题:有个同事把INTERNET权限声明漏了,结果App在模拟器上跑得好好的,真机上一连网就崩溃。排查了半天才发现是权限问题。所以啊,普通权限虽然简单,但漏了也会出大事。

3.3 危险权限:运行时权限机制的核心

危险权限才是重头戏。从Android 6.0开始,Google强制要求危险权限必须在运行时动态申请。为什么?你想想看,以前安装时一股脑全授权,用户根本不知道App在背后干了什么。

危险权限被分成了几个组,我列一下常见的:

  • 日历:READ_CALENDAR、WRITE_CALENDAR
  • 相机:CAMERA
  • 联系人:READ_CONTACTS、WRITE_CONTACTS
  • 位置:ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION
  • 麦克风:RECORD_AUDIO
  • 电话:READ_PHONE_STATE、CALL_PHONE
  • 传感器:BODY_SENSORS
  • 短信:SEND_SMS、READ_SMS
  • 存储:READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE

这里有个坑:权限组机制。同一个组里的权限,只要用户授权了一个,同组的其他权限也会自动获得授权。比如用户同意了CAMERA,那么同组的其他相机相关权限就不需要再弹窗了。但要注意,这只是行为上的便利,不代表你可以偷懒不声明。

3.4 运行时权限申请流程

我建议你把这个流程记牢,因为几乎每个App都会用到。流程其实就三步:

  1. 检查权限是否已授予:用ContextCompat.checkSelfPermission()
  2. 如果未授予,发起请求:用ActivityCompat.requestPermissions()
  3. 处理回调结果:在onRequestPermissionsResult()里处理

来看一个完整的代码示例,我习惯这么写:

// 第一步:检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    // 第二步:判断是否需要向用户解释为什么需要这个权限
    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.CAMERA)) {
        // 这里可以弹一个自定义对话框解释原因
        showExplanationDialog();
    } else {
        // 直接请求权限
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.CAMERA},
                REQUEST_CAMERA_PERMISSION);
    }
} else {
    // 权限已授予,直接执行操作
    openCamera();
}

// 第三步:处理回调
@Override
public void onRequestPermissionsResult(int requestCode,
        @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CAMERA_PERMISSION) {
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            openCamera();
        } else {
            // 用户拒绝了权限
            Toast.makeText(this, "相机权限被拒绝,无法拍照", Toast.LENGTH_SHORT).show();
        }
    }
}
我的小技巧:我习惯把权限请求封装成一个工具类,这样每个Activity里就不用重复写这些样板代码了。另外,shouldShowRequestPermissionRationale这个方法很关键——用户第一次拒绝后,第二次请求前最好解释一下为什么需要这个权限,用户体验会好很多。

3.5 签名权限:自家人的通行证

签名权限,说白了就是「只有跟我用同一个签名密钥签名的App才能用」。这在系统级开发或者同一个公司的应用套件里很常见。

举个例子,我在做车机系统时,有个系统服务需要暴露给特定的系统应用使用。这时候用签名权限就特别合适:

<permission android:name="com.example.car.ACCESS_VEHICLE_DATA"
    android:protectionLevel="signature" />

<uses-permission android:name="com.example.car.ACCESS_VEHICLE_DATA" />

只有跟定义这个权限的应用使用相同签名的App,才能获取到这个权限。其他App就算在Manifest里声明了也没用,系统直接忽略。

注意:签名权限一旦定义,就不能轻易更改签名。我曾经见过一个项目,因为换了签名密钥,导致所有依赖签名权限的模块全部失效。那场面,简直是灾难。

3.6 特权权限:系统预置应用的专属

特权权限,是Android里最高级别的权限。它只对系统预置应用(放在/system/priv-app目录下的)开放。普通应用就算声明了也没用。

常见的特权权限有:

  • WRITE_SETTINGS:修改系统设置
  • INSTALL_PACKAGES:安装或卸载应用
  • CHANGE_CONFIGURATION:修改系统配置
  • REBOOT:重启设备

在车机系统里,特权权限用得比较多。比如车载设置应用需要修改系统音量、亮度等设置,就需要WRITE_SETTINGS权限。但普通第三方导航App,是拿不到这个权限的。

我记得有一次,客户要求第三方App能直接修改系统音量。我解释了半天,说这个权限只有系统预置应用才能用。最后解决方案是:我们提供一个系统级的音量控制服务,通过AIDL接口暴露给第三方App调用。这样既安全又合规。

3.7 避坑指南:我踩过的那些坑

我曾经遇到过一个问题:有个App在Android 9上运行正常,到了Android 10上就闪退。排查了半天,发现是Android 10开始,READ_PHONE_STATE权限被移出了危险权限组,变成了特殊权限。也就是说,不能再用常规的运行时权限申请流程了,需要引导用户去系统设置里手动开启。

所以啊,做Android开发一定要关注每个大版本的权限变化。Google每年都在收紧权限,稍不注意就会踩坑。

另外,还有一个常见问题:权限被用户永久拒绝后怎么办? 如果用户勾选了「不再询问」,那么shouldShowRequestPermissionRationale会返回false,再次调用requestPermissions也不会弹窗。这时候只能引导用户去系统设置里手动开启权限。

// 检测是否被永久拒绝
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
        Manifest.permission.CAMERA)) {
    // 跳转到应用设置页面
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivity(intent);
}

3.8 总结一下

四种权限,各有各的用途。普通权限最省心,危险权限最麻烦,签名权限最安全,特权权限最强大。在实际开发中,我建议你遵循一个原则:最小权限原则——只申请你真正需要的权限,不要贪多。

特别是做车机系统,权限管控比手机更严格。因为车机涉及车辆控制、驾驶安全,一旦权限滥用,后果不堪设想。嗯,今天就先聊到这里,下一章我们讲讲权限的动态管理和最佳实践。