请求应用权限

每个 Android 应用都在访问权受限的沙盒中运行。如果您的应用需要使用自身沙盒以外的资源或信息,您可以声明权限并设置一项权限请求来获取所需访问权限。以下步骤是权限使用工作流的一部分。

如果您声明了任何危险权限,并且您的应用安装在搭载 Android 6.0(API 级别 23)或更高版本的设备上,那么您必须按照本指南中的步骤在运行时请求这些危险权限。

如果您没有声明任何危险权限,或者您的应用安装在搭载 Android 5.1(API 级别 22)或更低版本的设备上,则系统会自动授予相应的权限,您无需完成本页剩下的任何步骤。

基本原则

在运行时请求权限的基本原则如下:

  • 当用户开始与需要相关权限的功能互动时,在具体使用情境下请求权限。
  • 不要阻止用户使用应用。始终提供选项供用户取消与权限相关的指导界面流程。
  • 如果用户拒绝或撤消某项功能所需的权限,请适当降级您的应用以便让用户可以继续使用您的应用(可能通过停用需要权限的功能来实现)。
  • 不要对系统行为做任何假设。例如,假设某些权限会出现在同一个权限组中。权限组的作用只是在应用请求密切相关的多个权限时,帮助系统尽可能减少向用户显示的系统对话框数量。

请求权限的工作流

在应用中声明并请求运行时权限之前,请评估您的应用是否需要这样做。您无需声明任何权限就可以在您的应用中实现很多用例,例如拍照、暂停媒体播放和展示相关广告。

如果您确定您的应用需要声明和请求运行时权限,请完成以下步骤:

  1. 在应用的清单文件中,声明应用可能需要请求的权限
  2. 设计应用的用户体验,使应用中的特定操作与特定运行时权限相关联。应当让用户知道哪些操作可能会要求他们向您的应用授予访问其私人数据的权限。
  3. 等待用户调用应用中需要访问特定用户私人数据的任务或操作。届时,您的应用可以请求访问相应数据所需的运行时权限。
  4. 检查用户是否已授予应用所需的运行时权限。如果已授权,那么您的应用可以访问用户私人数据。如果没有,请继续执行下一步。

    每次执行需要该权限的操作时,您都必须检查自己是否具有该权限。

  5. 检查您的应用是否应向用户显示理由,说明您的应用需要用户授予特定运行时权限的原因。如果系统确定您的应用不应显示理由,请继续直接执行下一步,无需显示界面元素。

    不过,如果系统确定您的应用应该显示一个理由,请在界面元素中向用户显示理由,明确说明您的应用试图访问哪些数据,以及应用获得运行时权限后可为用户提供哪些好处。用户确认理由后,请继续执行下一步。

  6. 请求您的应用访问用户私人数据所需的运行时权限。系统会显示运行时权限提示,例如权限概览页面上显示的提示。

  7. 检查用户的响应,他们可能会选择同意或拒绝授予运行时权限。

  8. 如果用户向您的应用授予权限,您就可以访问用户私人数据。如果用户拒绝授予该权限,请适当降低应用体验,使应用在未获得受该权限保护的信息时也能向用户提供功能。

图 1 说明了与此过程相关的工作流和决策组:

图 1. 在 Android 上声明和请求运行时权限的工作流。

确定应用是否已获得权限

如需检查用户是否已向您的应用授予特定权限,请将该权限传入 ContextCompat.checkSelfPermission() 方法。根据您的应用是否具有相应权限,此方法会返回 PERMISSION_GRANTEDPERMISSION_DENIED

说明您的应用为何需要获取权限

系统在您调用 requestPermissions() 时显示的权限对话框将说明应用需要哪些权限,但不会解释为何需要这些权限。在某些情况下,用户可能会感到困惑。因此,最好在调用 requestPermissions() 之前向用户解释应用需要相应权限的原因。

研究表明,如果用户知道应用需要相应权限的原因,他们会更容易接受权限请求。用户研究表明:

…用户是否愿意为某个移动应用授予给定权限,在很大程度上受此类权限关联用途的影响。例如,用户是否愿意授予访问其位置信息的权限将取决于该权限请求是否为支持应用的核心功能所必需,或者应用是否会与广告网络或分析公司分享此信息。1

卡内基梅隆大学 (CMU) 的 Jason Hong 教授在与他人一起深入探讨此主题后,得出一个一般性结论:

…与只是告诉用户应用正在使用其位置信息相比,如果用户知道应用为什么使用像他们的位置这样敏感的信息(例如,用于定位广告),用户会更容易接受。1

因此,如果您仅使用归入权限组的一小部分 API 调用,明确列出您使用哪些权限以及使用原因会非常有用。例如:

  • 如果您仅使用粗略位置信息,请在应用说明或应用的帮助文章中告知用户。
  • 如果您需要访问短信以接收身份验证码,从而防止用户被欺诈,请在应用说明中和应用首次需要访问这类数据时告知用户。

    注意:如果您的应用以 Android 8.0(API 级别 26)或更高版本为目标平台,请不要在验证用户凭据过程中请求 READ_SMS 权限,而应使用 createAppSpecificSmsToken() 生成应用特定的令牌,然后将此令牌传递给可以发送验证短信的其他应用或服务。

在特定条件下,让用户实时了解应用在访问敏感数据也是非常有益的。例如,如果您要使用相机或麦克风,通常最好在应用中的某个位置或在通知栏中(如果应用正在后台运行)使用通知图标通知用户,避免让您看上去是在偷偷地收集数据。

最后,如果您需要请求权限以便在应用中运行某项功能,但用户不清楚原因,则需要找到一种方法让用户知道您为什么需要最敏感的权限。

如果 ContextCompat.checkSelfPermission() 方法返回 PERMISSION_DENIED,请调用 shouldShowRequestPermissionRationale()。如果此方法返回 true,请向用户显示指导界面,在此界面中说明用户希望启用的功能为何需要特定权限。

此外,如果您的应用请求与位置信息、麦克风或相机相关的权限,请考虑说明该应用需要访问这些信息的原因

请求权限

用户查看指导界面后或者 shouldShowRequestPermissionRationale() 的返回值表明您这次不需要显示指导界面后,您可以请求权限。用户会看到系统权限对话框,并可在其中选择是否向您的应用授予特定权限。

按照历来的做法,您可以在权限请求过程中自行管理请求代码,并将此请求代码包含在您的权限回调逻辑中。另一种选择是使用 AndroidX 库中包含的 RequestPermission 协定类,您可在其中允许系统替您管理权限请求代码。由于使用 RequestPermission 协定类可简化逻辑,因此,建议您尽可能使用该方法。

允许系统管理权限请求代码

如需允许系统管理与权限请求相关联的请求代码,请在您模块的 build.gradle 文件中添加以下库的依赖项:

然后,您可以使用以下某个类:

以下步骤显示了如何使用 RequestPermission 协定类。使用 RequestMultiplePermissions 协定类的流程基本与此相同。

  1. 在 activity 或 fragment 的初始化逻辑中,将 ActivityResultCallback 的实现传入对 registerForActivityResult() 的调用。ActivityResultCallback 定义应用如何处理用户对权限请求的响应。

    保留对 registerForActivityResult()(类型为 ActivityResultLauncher)的返回值的引用。

  2. 如需在必要时显示系统权限对话框,请对您在上一步中保存的 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.
    }
}

请求多项权限

请求位置权限时,请遵循与请求任何其他运行时权限相同的最佳做法。请求位置权限时的一个重要区别在于,系统中包含与位置相关的多项权限。具体请求哪项权限以及请求相关权限的方式取决于应用用例的位置信息要求。

前台位置信息

如果应用的某项功能仅分享或接收一次位置信息,或者只在特定的一段时间内分享或接收位置信息,则该功能需要前台位置信息访问权限。以下是此类情况的一些示例:

  • 在导航应用中,某项功能可让用户查询精细导航路线。
  • 在即时通讯应用中,某项功能可让用户与其他用户分享自己目前所在的位置。

如果应用的功能在下列某种情况下访问设备的当前位置信息,系统就会认为应用需要使用前台位置信息:

  • 属于应用的某个 Activity 可见。
  • 应用的某个前台服务正在运行中。当有前台服务在运行时,系统会显示一条常驻通知来提醒用户注意。当应用被置于后台时(例如当用户按设备上的主屏幕按钮或关闭设备的显示屏时),其位置信息访问权限会得到保留。

    此外,建议您声明 location前台服务类型,如以下代码段所示。在 Android 10(API 级别 29)及更高版本中,您必须声明此前台服务类型。

    <!-- Recommended for Android 9 (API level 28) and lower. -->
    <!-- Required for Android 10 (API level 29) and higher. -->
    <service
        android:name="MyNavigationService"
        android:foregroundServiceType="location" ... >
        <!-- Any inner elements would go here. -->
    </service>
    

当应用请求 ACCESS_COARSE_LOCATION 权限或 ACCESS_FINE_LOCATION 权限时(如以下代码段所示),就是在声明需要获取前台位置信息:

<manifest ... >
  <!-- Always include this permission -->
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

  <!-- Include only if your app benefits from precise location access. -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

后台位置信息

如果应用中的某项功能会不断与其他用户分享位置信息或使用 Geofencing API,则该应用需要后台位置信息访问权限。以下是此类情况的几个示例:

  • 在家庭位置信息分享应用中,某项功能可让用户与家庭成员持续分享位置信息。
  • 在 IoT 应用中,某项功能可让用户配置自己的家居设备,使其在用户离家时关机并在用户回家时重新开机。

除了前台位置信息部分所述的情况之外,如果应用在任何其他情况下访问设备的当前位置信息,系统就会认为应用需要使用后台位置信息。后台位置信息精确度与前台位置信息精确度相同,具体取决于应用声明的位置信息权限。

在 Android 10(API 级别 29)及更高版本中,您必须在应用的清单中声明 ACCESS_BACKGROUND_LOCATION 权限,以便请求在运行时于后台访问位置信息。在较低版本的 Android 系统中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。

<manifest ... >
  <!-- Required only when requesting background location access on
       Android 10 (API level 29) and higher. -->
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

处理权限请求遭拒情况

如果用户拒绝了权限请求,应用应该帮助用户了解拒绝授予权限的影响。具体而言,应用应该让用户知道因缺少权限而无法使用哪些功能。在处理这种情况时,请牢记以下最佳做法:

  • 引导用户的注意力。在应用界面中突出显示因为应用没有获得必要的权限而受限的功能所在的具体部分。以下列举了几个例子说明您可以采取的做法:

    • 在原本用于显示该功能的结果或数据的位置显示一条消息。
    • 显示一个包含错误图标并带有相应错误颜色的不同按钮。
  • 内容要具体。显示的消息不要空泛,而要指出因为应用没有获得必要的权限而无法使用的具体功能。

  • 不要阻止界面显示。换言之,不要显示全屏警告消息,让用户根本无法继续使用您的应用。

提示:即使在权限请求遭拒后,您的应用也应尽可能提供最佳的用户体验。例如,即使麦克风使用权限请求遭拒,您仍应全面提高文本功能的易用性。

与此同时,您的应用还应尊重用户拒绝授予权限的决定。从 Android 11(API 级别 30)开始,在应用安装到设备上后,如果用户在使用过程中多次针对某项特定的权限点按拒绝,那么在您的应用再次请求该权限时,用户将不会看到系统权限对话框。该操作表示用户希望“不再询问”。在之前的版本中,除非用户先前已选中“不再询问”对话框或选项,否则每当您的应用请求权限时,用户都会看到系统权限对话框。

如果用户多次拒绝某项权限请求,则会被视为永久拒绝请求。仅在用户需要使用特定功能时提示用户授予权限,这一点非常重要;否则,您可能会无意中失去重新请求权限的能力。

在某些情况下,系统可能会自动拒绝权限,而无需用户执行任何操作(同样,系统也可能会自动授予权限)。请千万不要对系统的自动行为做出任何假设。每当应用需要使用的功能需要权限时,您都应该检查应用是否仍被授予该权限。

如需在请求应用权限时提供最佳用户体验,另请参阅应用权限最佳做法

单次授权

该对话框中有三个按钮,其中第二个按钮为“仅限这一次”。
图 2. 应用请求单次授权时显示的系统对话框。

从 Android 11(API 级别 30)开始,每当您的应用请求与位置、麦克风或相机相关的权限时,面向用户的权限对话框都会包含仅限这一次选项,如图 2 所示。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。

然后,应用可以在一段时间内访问相关数据,具体时间取决于应用的行为和用户的操作:

  • 当应用的 activity 可见时,应用可以访问相关数据。
  • 如果用户将应用转为后台运行,应用可以在短时间内继续访问相关数据。
  • 如果您在 activity 可见时启动了一项前台服务,并且用户随后将您的应用转到后台,那么您的应用可以继续访问相关数据,直到该前台服务停止。

应用进程在权限被撤消时终止

如果用户撤消单次授权(例如在系统设置中撤消),无论您是否启动了前台服务,应用都无法访问相关数据。与任何权限一样,如果用户撤消了应用的单次授权,应用进程就会终止。

当用户下次打开应用并且应用中的某项功能请求访问位置信息、麦克风或摄像头时,系统会再次提示用户授予权限。

Android 自动重置未使用的应用的权限

如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台并且数月未使用,系统会通过自动重置用户已授予应用的运行时敏感权限来保护用户数据。如需了解详情,请参阅有关应用休眠的指南。

在必要时请求成为默认处理程序

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

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

授予所有运行时权限以进行测试

如需在您在模拟器或测试设备上安装应用时自动授予所有运行时权限,请为 adb shell install 命令使用 -g 选项,如以下代码段所示:

adb shell install -g PATH_TO_APK_FILE

其他资源

如需详细了解权限,请阅读以下文章:

如需详细了解如何请求权限,请下载以下示例应用:

  • Android RuntimePermissionsBasic 示例 Java | Kotlin