每款 Android 应用都在访问权受限的沙盒中运行。如果您的应用需要使用自身沙盒以外的资源或信息,您可以声明权限并设置提供此访问权限的权限请求。以下步骤是权限使用工作流程的一部分。
如果您声明了任何危险权限,并且您的应用安装在搭载 Android 6.0(API 级别 23)或更高版本的设备上,则必须按照本指南中的步骤在运行时请求这些危险权限。
如果您没有声明任何危险权限,或者您的应用安装在搭载 Android 5.1(API 级别 22)或更低版本的设备上,则系统会自动授予相应的权限,您无需完成本页剩下的任何步骤。
基本原则
在运行时请求权限的基本原则如下:
- 当用户开始与需要相关权限的功能互动时,在具体使用情境下请求权限。
- 不要阻止用户使用应用。始终提供选项供用户取消与权限相关的指导界面流程。
- 如果用户拒绝或撤消某项功能所需的权限,请适当降级您的应用以便让用户可以继续使用您的应用(可能通过停用需要该权限的功能来实现)。
- 不要假设任何系统行为。例如,假设权限出现在同一个权限组中。在应用请求密切相关的权限时,权限组仅能帮助系统尽可能减少向用户显示的系统对话框数量。
请求权限的工作流
在应用中声明并请求运行时权限之前,请评估您的应用是否需要这样做。您可以在您的应用中实现很多用例,例如拍照、暂停媒体播放和展示相关广告,而无需声明任何权限。
如果您确定您的应用需要声明和请求运行时权限,请完成以下步骤:
- 在应用的清单文件中,声明应用可能需要请求的权限。
- 设计应用的用户体验,使应用中的特定操作与特定运行时权限相关联。用户应该知道哪些操作可能会要求他们为您的应用授予访问用户私人数据的权限。
- 等待用户调用应用中需要访问特定用户私人数据的任务或操作。届时,您的应用可以请求访问相应数据所需的运行时权限。
检查用户是否已授予应用所需的运行时权限。如果是,那么您的应用可以访问用户私人数据。如果不是,请继续执行下一步。
每次执行需要该权限的操作时,您必须检查自己是否具有该权限。
检查您的应用是否应向用户显示理由,说明您的应用需要用户授予特定运行时权限的原因。如果系统确定您的应用不应显示理由,请继续直接执行下一步,而不显示界面元素。
不过,如果系统确定您的应用应该显示一个理由,请在界面元素中向用户显示理由。此理由应该明确说明您的应用尝试访问哪些数据,以及应用可在授予运行时权限的情况下为用户提供哪些好处。用户确认理由后,请继续执行下一步。
检查用户的响应,无论他们是选择授予还是拒绝运行时权限。
如果用户向您的应用授予权限,您就可以访问用户私人数据。如果用户拒绝授予该权限,请适当降低应用体验,即使没有受该权限保护的信息,也使其向用户提供功能。
图 1 说明了与此过程相关的工作流和决策组:
确定应用是否已获得权限
如需检查用户是否已向您的应用授予特定权限,请将该权限传入 ContextCompat.checkSelfPermission()
方法。根据您的应用是否具有相应权限,此方法会返回 PERMISSION_GRANTED
或 PERMISSION_DENIED
。
说明您的应用为何需要获取权限
如果 ContextCompat.checkSelfPermission()
方法返回 PERMISSION_DENIED
,请调用 shouldShowRequestPermissionRationale()
。如果此方法返回 true
,请向用户显示指导界面。请在此界面中说明用户希望启用的功能为何需要特定权限。
请求权限
用户查看指导界面后或者 shouldShowRequestPermissionRationale()
的返回值表明您这次不需要显示指导界面后,您可以请求权限。用户会看到系统权限对话框,并可在其中选择是否向您的应用授予特定权限。
按照历来的做法,您可以在权限请求过程中自行管理请求代码,并将此请求代码包含在您的权限回调逻辑中。另一种选择是使用 AndroidX 库中包含的 RequestPermission
协定类,您可在其中允许系统代为管理权限请求代码。由于使用 RequestPermission
协定类可简化逻辑,因此,建议您尽可能使用该方法。
允许系统管理权限请求代码
如需允许系统管理与权限请求相关联的请求代码,请在您模块的 build.gradle
文件中添加 androidx.activity
库的依赖项。请使用该库的 1.2.0 版或更高版本。
然后,您可以使用以下某个类:
- 如需请求一项权限,请使用
RequestPermission
。 - 如需同时请求多项权限,请使用
RequestMultiplePermissions
。
以下步骤显示了如何使用 RequestPermission
协定类。使用 RequestMultiplePermissions
协定类的流程几乎与此相同。
在 Activity 或 Fragment 的初始化逻辑中,将
ActivityResultCallback
的实现传入对registerForActivityResult()
的调用。ActivityResultCallback
定义应用如何处理用户对权限请求的响应。保留对
registerForActivityResult()
(类型为ActivityResultLauncher
)的返回值的引用。如需在必要时显示系统权限对话框,请对您在上一步中保存的
ActivityResultLauncher
实例调用launch()
方法。调用
launch()
之后,系统会显示系统权限对话框。当用户做出选择后,系统会异步调用您在上一步中定义的ActivityResultCallback
实现。注意:应用无法自定义调用
launch()
时显示的对话框。如需为用户提供更多信息或上下文,请更改应用的界面,以便让用户更容易了解应用中的功能为何需要特定权限。例如,您可以更改用于启用该功能的按钮中的文本。此外,系统权限对话框中的文本会提及与您请求的权限关联的权限组。此权限分组是为了让系统易于使用,您的应用不应依赖于特定权限组之内或之外的权限。
以下代码段展示了如何处理权限响应:
Kotlin
// Register the permissions callback, which handles the user's response to the // system permissions dialog. Save the return value, an instance of // ActivityResultLauncher. You can use either a val, as shown in this snippet, // or a lateinit var in your onAttach() or onCreate() method. val requestPermissionLauncher = registerForActivityResult(RequestPermission() ) { isGranted: Boolean -> if (isGranted) { // Permission is granted. Continue the action or workflow in your // app. } else { // Explain to the user that the feature is unavailable because the // features requires a permission that the user has denied. At the // same time, respect the user's decision. Don't link to system // settings in an effort to convince the user to change their // decision. } }
Java
// Register the permissions callback, which handles the user's response to the // system permissions dialog. Save the return value, an instance of // ActivityResultLauncher, as an instance variable. private ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(new RequestPermission(), isGranted -> { if (isGranted) { // Permission is granted. Continue the action or workflow in your // app. } else { // Explain to the user that the feature is unavailable because the // features requires a permission that the user has denied. At the // same time, respect the user's decision. Don't link to system // settings in an effort to convince the user to change their // decision. } });
以下代码段演示了检查权限并根据需要向用户请求权限的建议流程:
Kotlin
when { ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION ) == PackageManager.PERMISSION_GRANTED -> { // You can use the API that requires the permission. } shouldShowRequestPermissionRationale(...) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected. In this UI, // include a "cancel" or "no thanks" button that allows the user to // continue using your app without granting the permission. showInContextUI(...) } else -> { // You can directly ask for the permission. // The registered ActivityResultCallback gets the result of this request. requestPermissionLauncher.launch( Manifest.permission.REQUESTED_PERMISSION) } }
Java
if (ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION) == PackageManager.PERMISSION_GRANTED) { // You can use the API that requires the permission. performAction(...); } else if (shouldShowRequestPermissionRationale(...)) { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected. In this UI, // include a "cancel" or "no thanks" button that allows the user to // continue using your app without granting the permission. showInContextUI(...); } else { // You can directly ask for the permission. // The registered ActivityResultCallback gets the result of this request. requestPermissionLauncher.launch( Manifest.permission.REQUESTED_PERMISSION); }
自行管理权限请求代码
作为允许系统管理权限请求代码的替代方法,您可以自行管理权限请求代码。为此,请在对 requestPermissions()
的调用中添加请求代码。
以下代码段演示了如何使用请求代码来请求权限:
Kotlin
when { ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION ) == PackageManager.PERMISSION_GRANTED -> { // You can use the API that requires the permission. performAction(...) } shouldShowRequestPermissionRationale(...) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected. In this UI, // include a "cancel" or "no thanks" button that allows the user to // continue using your app without granting the permission. showInContextUI(...) } else -> { // You can directly ask for the permission. requestPermissions(CONTEXT, arrayOf(Manifest.permission.REQUESTED_PERMISSION), REQUEST_CODE) } }
Java
if (ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION) == PackageManager.PERMISSION_GRANTED) { // You can use the API that requires the permission. performAction(...); } else if (shouldShowRequestPermissionRationale(...)) { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected. In this UI, // include a "cancel" or "no thanks" button that allows the user to // continue using your app without granting the permission. showInContextUI(...); } else { // You can directly ask for the permission. requestPermissions(CONTEXT, new String[] { Manifest.permission.REQUESTED_PERMISSION }, REQUEST_CODE); }
当用户响应系统权限对话框后,系统就会调用应用的 onRequestPermissionsResult()
实现。系统会传入用户对权限对话框的响应以及您定义的请求代码,如以下代码段所示:
Kotlin
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { when (requestCode) { PERMISSION_REQUEST_CODE -> { // If request is cancelled, the result arrays are empty. if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // Permission is granted. Continue the action or workflow // in your app. } else { // Explain to the user that the feature is unavailable because // the features requires a permission that the user has denied. // At the same time, respect the user's decision. Don't link to // system settings in an effort to convince the user to change // their decision. } return } // Add other 'when' lines to check for other // permissions this app might request. else -> { // Ignore all other requests. } } }
Java
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case PERMISSION_REQUEST_CODE: // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission is granted. Continue the action or workflow // in your app. } else { // Explain to the user that the feature is unavailable because // the features requires a permission that the user has denied. // At the same time, respect the user's decision. Don't link to // system settings in an effort to convince the user to change // their decision. } return; } // Other 'case' lines to check for other // permissions this app might request. } }
处理权限请求遭拒情况
如果用户拒绝了权限请求,应用应该帮助用户了解拒绝授予权限的影响。具体而言,应用应该让用户知道因缺少权限而无法使用的功能。在处理这种情况时,请牢记以下最佳做法:
引导用户的注意力。在应用界面中突出显示因为应用没有必要的权限而受限的功能所在的具体部分。以下列举了几个例子说明您可以采取的做法:
- 在该功能的结果或数据会出现的位置显示一条消息。
- 显示一个包含错误图标并带有相应错误颜色的不同按钮。
内容要具体。显示的消息不要空泛;而要指出因为应用没有必要的权限而无法使用的具体功能。
不要阻止界面显示。换言之,不要显示全屏警告消息,让用户根本无法继续使用您的应用。
与此同时,您的应用还应尊重用户拒绝授予权限的决定。从 Android 11(API 级别 30)开始,在应用安装到设备上后,如果用户在使用过程中多次针对某项特定的权限点按拒绝,那么在您的应用再次请求该权限时,用户将不会看到系统权限对话框。该操作表示用户希望“不再询问”。在之前的版本中,除非用户先前已选中“不再询问”对话框或选项,否则每当您的应用请求权限时,用户都会看到系统权限对话框。
在某些情况下,系统可能会自动拒绝权限,而无需用户执行任何操作。(同样,系统也可能会自动授予权限。)请千万不要对系统的自动行为做出任何假设。每当应用需要使用的功能需要权限时,您都应该检查应用是否仍被授予该权限。
如需在请求应用权限时提供最佳用户体验,另请参阅应用权限最佳做法。
单次授权
从 Android 11(API 级别 30)开始,每当您的应用请求与位置、麦克风或相机相关的权限时,面向用户的权限对话框都会包含仅限这一次选项,如图 2 所示。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。
然后,应用可以在一段时间内访问相关数据,具体时间取决于应用的行为和用户的操作:
- 当应用的 Activity 可见时,应用可以访问相关数据。
- 如果用户将应用转为后台运行,应用可以在短时间内继续访问相关数据。
- 如果您在 Activity 可见时启动了一项前台服务,并且用户随后将您的应用转到后台,那么您的应用可以继续访问相关数据,直到该前台服务停止。
- 如果用户撤消单次授权(例如在系统设置中撤消),无论您是否启动了前台服务,应用都无法访问相关数据。与任何权限一样,如果用户撤消了应用的单次授权,应用进程就会终止。
当用户下次打开应用并且应用中的某项功能请求访问位置信息、麦克风或摄像头时,系统会再次提示用户授予权限。
自动重置未使用的应用的权限
如果应用以 Android 11(API 级别 30)或更高版本为目标平台并且数月未使用,系统会通过自动重置用户已授予应用的运行时敏感权限保护用户数据。此操作与用户在系统设置中查看权限并将应用的访问权限级别更改为拒绝的做法效果一样。
如果应用遵循了有关在运行时请求权限的最佳做法,那么您不必对应用进行任何更改。
请求用户停用自动重置功能
如果需要,您可以要求用户阻止系统重置应用的权限。如果用户希望应用主要在后台运行,即使用户不与应用互动,应用也能正常工作,那么此做法就非常有用。此类使用场景的示例如下:
- 提供健康内容。
- 同步数据。
- 与智能设备通信。
- 与配套设备配对。
如需将用户定向到系统设置中您应用的页面,请调用包含 Intent.ACTION_AUTO_REVOKE_PERMISSIONS
intent 操作的 intent。在此屏幕中,用户可以通过执行以下操作来阻止系统重置应用的权限:
- 点按权限,系统会加载应用权限设置屏幕。
- 关闭如果未使用此应用,移除相关权限选项,如图 3 所示。
确定是否已停用自动重置功能
如需检查是否已针对应用停用自动重置功能,请调用 isAutoRevokeWhitelisted()
。如果此方法返回 true
,则系统不会自动重置应用的权限。
在必要时请求成为默认处理程序
有些应用依赖于访问与通话记录和短信有关的敏感用户信息。如果您想请求特定于通话记录和短信的权限,并将应用发布到 Play 商店,您必须在请求这些运行时权限之前,提示用户将应用设置为核心系统功能的默认处理程序。
如需详细了解默认处理程序,包括有关如何向用户显示默认处理程序提示的指南,请参阅有关仅在默认处理程序中使用的权限的指南。
测试运行时权限
本部分介绍如何从多个方面测试运行时权限。
授予所有运行时权限
如需在您在模拟器或测试设备上安装应用时自动授予所有运行时权限,请为 adb shell install
命令使用 -g
选项,如以下代码段所示:
adb shell install -g PATH_TO_APK_FILE
自动重置应用的权限
如需验证系统是否重置了应用的权限,请执行以下操作:
保存系统重置应用权限所需等待的默认时长。这样,您就可以在测试后恢复此设置:
threshold=$(adb shell device_config get permissions \ auto_revoke_unused_threshold_millis2)
减少系统重置权限所需等待的时长。下面的示例对系统进行了修改,以致当您停止与应用互动后仅一秒钟,系统就会重置应用的权限:
adb shell device_config put permissions \ auto_revoke_unused_threshold_millis2 1000
手动调用自动重置进程,如以下代码段所示。在运行此命令之前,请确保测试设备已开启片刻(大约 45 秒钟)。
adb shell cmd jobscheduler run -u 0 -f \ com.google.android.permissioncontroller 2
验证应用能否处理自动重置事件。
恢复系统在自动重置应用权限之前需要等待的默认时长:
adb shell device_config put permissions \ auto_revoke_unused_threshold_millis2 $threshold
其他资源
如需详细了解权限,请阅读以下文章:
如需详细了解如何请求权限,请下载以下示例应用: