欢迎参加我们将于 6 月 3 日举行的 #Android11:Beta 版发布会

确定敏感数据访问需求

权限的作用是保护 Android 用户的隐私。Android 应用必须请求权限才能访问敏感用户数据(例如联系人和短信)以及某些系统功能(例如相机和互联网)。系统可能会自动授予权限,也可能会提示用户批准请求,具体取决于访问的功能。

Android 安全架构的设计主旨是:在默认情况下任何应用都没有权限执行会对其他应用、操作系统或用户带来不利影响的任何操作。这包括读取或或写入用户的私有数据(例如联系人或电子邮件)、读取或写入其他应用的文件、执行网络访问、使设备保持唤醒状态等。

本页概述了 Android 权限的工作原理,包括:如何向用户提供权限、安装时权限请求和运行时权限请求之间的差异、如何强制执行权限,以及权限类型和权限组。如果您只需要关于如何使用应用权限的方法指南,请参阅请求应用权限

权限审批

应用必须公开所需的权限,方法是在应用清单中包括 <uses-permission> 标记。例如,需要发送短信的应用会在清单中会包含以下行:

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

        <uses-permission android:name="android.permission.SEND_SMS"/>

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

如果您的应用在清单中列出普通权限(即不会给用户隐私或设备操作带来太大风险的权限),系统会自动将这些权限授予应用。

如果您的应用在清单中列出危险权限(即可能影响用户隐私或设备正常操作的权限),如上面的SEND_SMS 权限,则用户必须明确同意授予这些权限。

如需详细了解普通权限和危险权限,请参阅保护级别

危险权限的请求提示

仅危险权限需要用户同意。Android 请求用户授予危险权限的方式取决于用户设备上运行的 Android 版本和应用所搭载的系统版本。

运行时请求(Android 6.0 及更高版本)

如果设备搭载的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本,则在安装时用户不会收到任何应用权限的通知。您的应用必须在运行时请求用户授予危险权限。当应用请求权限时,用户会看到一个系统对话框(如左侧图 1 所示),告知用户应用正在尝试访问的权限组。该对话框包括拒绝允许按钮。

如果用户拒绝权限请求,则应用下次请求权限时,该对话框将包含一个复选框,选中它即表示用户不想再收到权限提示(请参阅右侧的图 1)。

图 1 初始权限对话框(左)和二次权限请求(包含关闭进一步请求的选项)(右)

如果用户勾选不再询问框并点按拒绝,则稍后尝试请求相同权限时,系统不再提示用户。

即使用户授予您应用所请求的权限,也不能始终依赖它。用户也可选择在系统设置中逐一启用和停用权限。您应始终在运行时检查并请求权限以防止发生运行时错误 (SecurityException)。

要详细了解如何处理运行时权限请求,请参阅请求应用权限

安装时请求(Android 5.1.1 及更低版本)

如果设备运行的是 Android 5.1.1(API 级别 22)或更低版本,或者应用在任何版本的 Android 上运行时其 targetSdkVersion 是 22 或更低版本,则系统将在安装时自动请求用户授予应用所有危险权限(请参见图 2)。

图 2. 安装时权限对话框

如果用户点击接受,将授予应用请求的所有权限。如果用户拒绝权限请求,系统将取消安装应用。

如果应用更新包括额外权限需求,则系统会在更新应用之前提示用户接受这些新权限。

有关请求权限的建议用户体验模式概览,请参阅应用权限最佳做法

要了解如何查看并请求用户权限,请参阅请求应用权限

访问敏感用户信息的请求提示

有些应用依赖于访问与通话记录和短信有关的敏感用户信息。如果您想请求特定于通话记录和短信的权限,并将应用发布到 Play 商店,则必须在请求这些运行时权限之前提示用户将应用设置为“默认处理程序”以获得核心系统功能。

如需详细了解默认处理程序(包括有关如何向用户显示默认处理程序提示的指南),请参阅有关仅在默认处理程序中使用的权限的指南

可选硬件功能的权限

访问某些硬件功能(如蓝牙或相机)需要应用权限。但是,实际上并非所有 Android 设备都有这些硬件功能。因此,如果您的应用请求 CAMERA 权限,则务必同时在清单中包括 <uses-feature> 标记以声明是否确实需要此功能。例如:

<uses-feature android:name="android.hardware.camera" android:required="false" />
    

如果对此功能声明 android:required="false",则 Google Play 允许您的应用安装在没有此功能的设备上。然后,您必须在运行时通过调用 PackageManager.hasSystemFeature() 检查当前设备是否具有该功能,并且正确地停用该功能(如果不可用)。

如果您不提供 <uses-feature> 标记,则当 Google Play 发现您的应用请求相应的权限时,会假定您的应用需要该功能。因此,它会从没有该功能的设备中过滤掉您的应用,就像您在 <uses-feature> 标记中声明了 android:required="true" 一样。

有关详情,请参阅 Google Play 和根据功能进行过滤

权限强制执行

权限不仅仅用于请求系统功能。应用提供的服务可强制执行自定义权限以限制谁可使用它们。有关声明自定义权限的更多信息,请参阅定义自定义应用权限

Activity 权限强制执行

使用 android:permission 属性应用于清单中 <activity> 标记的权限限制谁可以启动该 Activity。在 Context.startActivity()Activity.startActivityForResult() 期间检查权限。如果调用方没有所需的权限,则调用会抛出 SecurityException

服务权限强制执行

使用 android:permission 属性应用于清单中的 <service> 标记的权限限制谁可以启动或绑定到相关的 Service。权限在 Context.startService()Context.stopService()Context.bindService() 期间进行检查。如果调用方没有所需的权限,则调用会抛出 SecurityException

广播权限强制执行

使用 android:permission 属性应用于 <receiver> 标记的权限限制谁可以发送广播给相关的 BroadcastReceiverContext.sendBroadcast() 返回后检查权限,因为系统会尝试将提交的广播传递到指定的接收方。因此,权限失效不会导致向调用方抛回异常;只是不会传递该 Intent

同样,可以向 Context.registerReceiver() 提供权限来控制谁可以广播到以编程方式注册的接收器。另一方面,可以在调用 Context.sendBroadcast() 时提供权限来限制允许哪些广播接收器接收广播。

请注意,接收器和广播机构可能都需要权限。此时,这两项权限检查都必须通过后方可将 intent 传递到相关的目标。有关详情,请参阅利用权限限制广播

内容提供程序权限强制执行

使用 android:permission 属性应用于 <provider> 标签的权限限制谁可以访问 ContentProvider 中的数据。(内容提供程序有重要的附加安全工具可用,称为 URI 权限,将在后面介绍。)与其他组件不同,您可以设置两个单独的权限属性:android:readPermission 限制谁可以读取提供程序,android:writePermission 限制谁可以写入提供程序。请注意,如果提供程序有读取和写入权限保护,则仅拥有写入权限并不表示您可以读取提供程序。

第一次检索提供程序时将会检查权限(如果没有任何权限,将会抛出 SecurityException),对提供程序执行操作时也会检查权限。使用 ContentResolver.query() 需要拥有读取权限;使用 ContentResolver.insert()ContentResolver.update()ContentResolver.delete() 需要写入权限。在所有这些情况下,没有所需的权限将导致调用抛出 SecurityException

URI 权限

在与内容提供程序配合使用时,到目前为止所述的标准权限系统通常是不够的。内容提供程序可能需要通过读取和写入权限保护自己,而其直接客户端也需要将特定 URI 传给其他应用以便于它们运行。

典型的示例是电子邮件应用中的附件。应通过权限保护对电子邮件的访问,因为这是敏感的用户数据。但是,如果将图片附件的 URI 提供给图片查看器,该图片查看器不再有打开附件的权限,因为它没有理由拥有访问所有电子邮件的权限。

此问题的解决方法是采用按 URI 的权限机制:在启动 Activity 或返回结果给 Activity 时,调用方可以设置 Intent.FLAG_GRANT_READ_URI_PERMISSION 和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这将授予接收 Activity 访问 intent 中特定数据 URI 的权限,而不管它是否具有任何权限访问 intent 对应的内容提供程序中的数据。

此机制支持常见的能力式模型,其中用户交互(如打开附件、从列表中选择联系人等)推动临时授予细化的权限。这是一项关键功能,可将应用所需的权限缩小至只与其行为直接相关的权限。

要构建使其他应用对其在应用内操作负责的最安全的实现,应以此方式使用细化的权限并使用 android:grantUriPermissions 属性或 <grant-uri-permissions> 标签声明您的应用对它的支持。

如需更多信息,请参阅 Context.grantUriPermission()Context.revokeUriPermission()Context.checkUriPermission() 方法。

其他权限强制执行

可对任何服务调用强制执行任意细化的权限。这是使用 Context.checkCallingPermission() 方法来实现的。使用所需的权限字符串调用,它将返回一个整数,表示权限是否已授予当前的调用进程。请注意,仅在执行从另一个进程传入的调用(通常是通过从服务发布的 IDL 界面或者指定给另一进程的某种其他方式完成)时才可使用此方法。

检查权限还有许多其他有用的方法。如果您有另一个进程的进程 ID (PID),则可以使用 Context.checkPermission() 方法检查针对该 PID 的权限。如果您有另一个应用的软件包名称,则可以使用 PackageManager.checkPermission() 方法了解是否已为特定软件包授予特定权限。

自动权限调整

随着时间的推移,平台中可能会加入新的限制,要想使用特定 API,您的应用可能必须请求之前不需要的权限。因为现有应用假设可随意获取这些 API 的访问权限,所以 Android 可能会将新的权限请求应用到应用清单,以免在新平台版本上中断应用(从而将应用包括在“祖父豁免原则”中以获取权限)。Android 将根据为 targetSdkVersion 属性提供的值决定应用是否需要权限。如果该值低于在其中添加权限的版本,则 Android 会添加该权限。

例如,强制执行以 API 级别 19 开始的 READ_EXTERNAL_STORAGE 权限,用以限制访问共享存储空间。如果您的 targetSdkVersion 为 18 或更低版本,则会向更新的 Android 版本上的应用添加此权限。

警告:如果某权限自动添加到应用,则即使您的应用可能实际并不需要这些附加权限,Google Play 上的应用列表也会列出它们。为避免这种情况,并且移除您不需要的默认权限,请始终将 targetSdkVersion 更新至尽可能高的版本。可在 Build.VERSION_CODES 文档中查看各版本添加的权限。

保护级别

权限分为几个保护级别。保护级别影响是否需要运行时权限请求。

有三种保护级别影响第三方应用:普通、签名和危险权限。要查看特定权限所拥有的保护级别,请访问权限 API 参考页面

普通权限

“普通”权限涵盖以下情况:应用需要访问其沙盒外部的数据或资源,但对用户隐私或其他应用操作带来的风险很小。例如,设置时区的权限就是普通权限。

如果应用在其清单中声明其需要普通权限,系统在安装时会自动向应用授予该权限。系统不会提示用户授予普通权限,用户也无法撤消这些权限。

签名权限

系统在安装时授予这些应用权限,但仅会在尝试使用某权限的应用签名证书为定义该权限的同一证书时才会授予。

危险权限

“危险”权限涵盖以下情况:应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响。例如,能够读取用户的联系人属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。在用户批准该权限之前,您的应用无法提供依赖于该权限的功能。

要使用危险权限,您的应用必须在运行时提示用户授予权限。如需详细了解如何提示用户,请参阅危险权限请求提示

特殊权限

有许多权限的行为与普通权限及危险权限都不同。SYSTEM_ALERT_WINDOWWRITE_SETTINGS 特别敏感,因此大多数应用不应该使用它们。如果某应用需要其中一种权限,必须在清单中声明该权限,并发送请求用户授权的 intent。系统将向用户显示详细管理屏幕,以响应该 intent。

如需详细了解如何请求这些权限的详情,请参阅 SYSTEM_ALERT_WINDOWWRITE_SETTINGS 参考条目。

Android 系统提供的所有权限请参阅 Manifest.permission

权限组

权限根据设备的功能或特性分为多个组。在这一体系中,权限请求在组级别进行处理,一个权限组对应于应用清单中的多个权限声明。例如,短信组同时包括 READ_SMSRECEIVE_SMS 声明。通过这种方式对权限分组让用户能做出更有意义和明智的选择,而不会对复杂和技术性权限请求感到无所适从。

所有危险的 Android 权限都属于权限组。任何权限都可以属于权限组(不管保护级别是什么)。但是,权限组仅当权限危险时才影响用户体验。

如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当应用请求危险权限时,系统会发生以下行为:

  • 如果应用当前在权限组中没有任何权限,则系统会向用户显示权限请求对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只会说明应用需要访问设备的联系人。如果用户批准,系统将向应用授予其请求的权限。
  • 如果应用已在相同权限组中被授予另一危险权限,系统将立即授予该权限,不会与用户进行任何交互。例如,如果某应用之前已请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限,不向用户显示权限对话框。

注意:将来版本的 Android SDK 可能可以将特定权限从一个组移到另一个组。因此,不要根据这些权限组的结构设置应用的逻辑。

例如,自 Android 8.1(API 级别 27)起,READ_CONTACTS 位于与 WRITE_CONTACTS 相同的权限组中。如果您的应用请求 READ_CONTACTS 权限,然后请求 WRITE_CONTACTS 权限,不要假定系统可以自动授予 WRITE_CONTACTS 权限。

如果设备运行的是 Android 5.1(API 级别 22)或更低版本,或应用的 targetSdkVersion 是 22 或更低版本,则系统会在安装时要求用户授予权限。再次强调,系统只会向用户告知应用需要的权限组,而不告知具体权限。例如,当应用请求 READ_CONTACTS 时,安装对话框会列出“联系人”组。当用户接受时,仅向应用授予 READ_CONTACTS 权限。

注意:您的应用仍需要明确请求它需要的每项权限,即使用户已授予同一组中的其他权限。此外,权限分组在将来的 Android 版本中可能会发生变化。您的代码的逻辑不应依赖于同一组中的一组特定权限。

查看应用的权限

您可以使用“设置”应用和 shell 命令 adb shell pm list permissions 查看系统中当前定义的所有权限。要使用“设置”应用,请依次转至设置 > 应用。选择一个应用并向下滚动查看该应用使用的权限。对于开发者,adb '-s' 选项以类似于用户所见即所得的形式显示权限:

    $ adb shell pm list permissions -s
    All Permissions:

    Network communication: view Wi-Fi state, create Bluetooth connections, full
    internet access, view network state

    Your location: access extra location provider commands, fine (GPS) location,
    mock location sources for testing, coarse (network-based) location

    Services that cost you money: send SMS messages, directly call phone numbers

    ...
    

在模拟器或测试设备上安装应用时,您也可以使用 adb -g 选项自动授予所有权限:

    $ adb shell install -g MyApp.apk
    

其他资源

  • 请求应用权限:请求应用权限的方法指南。
  • 隐含功能要求的权限:有关如何请求某些权限的信息会隐式将您的应用限制于包含相应硬件或软件功能的设备。
  • <uses-permission>:声明应用所需权限的清单标记的 API 参考文档。
  • 设备兼容性:介绍 Android 如何在不同类型的设备上工作,以及如何针对不同设备优化您的应用,或如何限制您的应用在不同设备上的可用性。
  • Android 安全概述:详细讨论了 Android 平台安全模式。
  • “妈妈,我可以吗?”请求权限:2015 Android 开发者峰会上的这个视频描述了请求权限的最佳做法。
  • Android M 权限:Google I/O 2015 上的这个视频解释了在 Android 6.0 中对权限模型进行的更改。