2、权限声明:AndroidManifest权限声明、uses-permission标签详解、权限保护级别

好,我们直接进入正题。权限声明这件事,说白了就是告诉系统:「我的App需要用到这些敏感能力」。你如果不声明,系统根本不会给你授权。我在早期做开发时,就犯过这种低级错误——明明代码里调用了相机,却忘了在清单文件里加权限声明,结果一运行就崩溃。嗯,这种坑踩过一次就记住了。

2.1 AndroidManifest中的权限声明

所有的权限声明,都写在 AndroidManifest.xml 文件里。这个文件是Android应用的「身份证」,系统在安装App时就会读取它。

声明权限的位置,是在 <manifest> 根标签下,<application> 标签之前。我习惯把权限声明放在文件最前面,这样一眼就能看到这个App到底要了哪些权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <!-- 权限声明写在这里 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application>
        ...
    </application>
</manifest>

你想想看,如果你不写 <uses-permission>,系统根本不知道你的App需要相机。运行时调用 Camera.open() 就会直接抛异常。所以,第一步就是别漏掉声明。

2.2 uses-permission 标签详解

<uses-permission> 这个标签看起来简单,其实有几个属性值得注意。

属性 说明 是否必填
android:name 权限的完整名称,如 android.permission.CAMERA
android:maxSdkVersion 指定该权限只在某个API级别以下生效

这里我要重点说一下 android:maxSdkVersion。这个属性很多人不知道,或者知道了也不怎么用。但我个人觉得,它在某些场景下非常实用。

举个例子:在Android 10(API 29)之前,读取设备ID需要 READ_PHONE_STATE 权限。但从Android 10开始,这个权限被收紧了,普通App根本拿不到IMEI。如果你还在清单里声明了这个权限,用户在Google Play上可能会看到「你的App需要读取电话状态」,这很劝退。

我的做法是:

<uses-permission
    android:name="android.permission.READ_PHONE_STATE"
    android:maxSdkVersion="28" />

这样,在Android 9及以下设备上,权限仍然生效;在Android 10及以上,系统直接忽略这个声明。用户看不到,也不会被骚扰。嗯,这个小技巧,我在好几个项目里都用过。

小提示: 如果你的App目标API级别是31或更高,READ_PHONE_STATE 权限基本用不上了。建议直接用 maxSdkVersion 把它限制住。

2.3 权限保护级别

权限保护级别,是Android系统用来判断「这个权限到底有多敏感」的标准。说白了,就是给权限分个等级。

Android定义了四种保护级别:

保护级别 含义 是否需要用户同意
normal 普通权限,风险低 否,系统自动授予
dangerous 危险权限,涉及用户隐私 是,运行时弹窗询问
signature 签名权限,只有相同签名的App才能使用 否,系统自动判断
signatureOrSystem 系统级签名权限 否,仅系统App可用

这里我重点讲两个:normaldangerous。因为这两个是我们在日常开发中最常打交道的。

2.3.1 normal 权限

普通权限,比如 INTERNETACCESS_NETWORK_STATE。这些权限不涉及用户隐私,系统在安装时就直接授予了。你不需要在代码里动态申请,声明了就能用。

我记得有一次,一个同事跑来问我:「为什么我加了 INTERNET 权限,App还是连不上网?」我一看,他把权限声明写在了 <application> 标签里面。嗯,位置错了,系统根本读不到。

注意: 普通权限虽然不需要动态申请,但声明位置必须正确。写在 <application> 内部是无效的。

2.3.2 dangerous 权限

危险权限,比如 CAMERAACCESS_FINE_LOCATIONREAD_CONTACTS。这些权限涉及用户隐私,从Android 6.0(API 23)开始,必须运行时动态申请。

我曾经在一个项目中,因为没处理好权限拒绝的情况,导致用户点击「拒绝」后,App直接闪退。后来我加了一个判断:如果用户拒绝了权限,就弹出一个解释对话框,告诉用户为什么需要这个权限。用户体验好了很多。

危险权限还有一个特点:它们属于权限组。比如 CAMERA 属于 CAMERA 组,ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 都属于 LOCATION 组。如果用户同意了 ACCESS_FINE_LOCATION,系统会自动授予同组的 ACCESS_COARSE_LOCATION。这个机制,我在做定位功能时利用过。

2.3.3 signature 权限

签名权限,这个比较特殊。它只允许相同签名的App之间互相调用。比如你开发了两个App,它们用同一个签名文件打包。那么App A可以声明一个 signature 级别的权限,App B声明 <uses-permission> 后就能直接调用,不需要用户同意。

这个机制,我在做公司内部组件化方案时用过。两个模块之间需要通信,但又不想暴露给第三方App。用 signature 权限,既安全又省事。

2.4 知识体系结构图

下面这张图,我把权限声明的核心逻辑梳理了一下。你可以对照着看,心里会更清楚。

权限声明核心知识体系 Android权限声明 AndroidManifest.xml <uses-permission> 权限保护级别 位置:manifest下 application前 多个权限 name属性 maxSdkVersion 权限名称 normal dangerous signature 核心原则:声明位置要正确,权限级别要分清 dangerous权限必须运行时动态申请

2.5 避坑指南

最后,我分享几个实际项目中踩过的坑:

  • 权限声明位置错误: 我曾经把 <uses-permission> 写在了 <application> 里面,结果权限完全不生效。系统在解析清单文件时,只认 <manifest> 下的直接子标签。
  • 忘记处理权限拒绝: 在Android 6.0以上,用户随时可以撤销权限。如果你的App没有处理权限被拒绝的情况,直接调用相关API会崩溃。我建议每次使用危险权限前,都检查一下权限状态。
  • 滥用 maxSdkVersion 这个属性虽然好用,但别乱用。如果你不确定某个权限在新版本中是否被移除,最好查一下官方文档。我曾经因为设置错了 maxSdkVersion,导致在某个版本上功能异常。
核心总结: 权限声明是Android安全的第一道门槛。声明位置要对,保护级别要分清,dangerous权限要动态申请。记住这三点,大部分权限问题都能避免。

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