运行时请求位置信息访问权限

当您的应用中的某个功能需要位置信息访问权限时,请等到用户与该功能互动后再提出权限请求。这种工作流程遵循了运行时在情境中请求权限的最佳实践,具体说明请参阅解释如何请求应用权限的指南。

图 1 展示了如何执行此过程的一个示例。该应用包含一项需要前台位置信息访问权限的“分享位置信息”功能。但是,只有在用户选择 分享位置信息按钮后,应用才会请求位置信息权限。

After the user selects the Share Location button, the
    system's location permission dialog appears
图 1. 需要前台位置信息访问权限的位置信息分享功能。用户选择仅在使用应用期间允许后,即可启用此功能。

用户只能授予大概位置信息访问权限

在 Android 12(API 级别 31)或更高版本中,即使您的应用请求了 ACCESS_FINE_LOCATION 运行时权限,用户也可以请求您的应用仅检索大概位置信息。

为了应对这种潜在的用户行为,请勿单独请求 ACCESS_FINE_LOCATION 权限。而是在一次运行时请求中同时请求 ACCESS_FINE_LOCATION 权限和 ACCESS_COARSE_LOCATION 权限。如果您尝试仅请求 ACCESS_FINE_LOCATION,系统会在 Android 12 的某些版本中忽略该请求。如果您的应用以 Android 12 或更高版本为目标平台,系统会在 Logcat 中记录以下错误消息

ACCESS_FINE_LOCATION must be requested with ACCESS_COARSE_LOCATION.

当您的应用同时请求 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 时,系统权限对话框会为用户提供以下选项

  • 精确:允许您的应用获取精确位置信息。
  • 大概:允许您的应用仅获取大概位置信息。

图 3 显示,对话框为这两个选项提供了视觉提示,以帮助用户进行选择。用户决定位置信息精度后,可以点击三个按钮中的一个来选择权限授予的持续时间。

在 Android 12 及更高版本上,无论应用的 target SDK 版本如何,用户都可以前往系统设置来为任何应用设置首选位置信息精度。即使您的应用安装在运行 Android 11 或更低版本的设备上,然后用户将设备升级到 Android 12 或更高版本,情况也是如此。

The dialog refers only to approximate location and
         contains 3 buttons, one above the other
图 2. 您的应用仅请求 ACCESS_COARSE_LOCATION 时出现的系统权限对话框。
The dialog has 2 sets of options, one above the other
图 3. 您的应用在一次运行时请求中同时请求 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 时出现的系统权限对话框。

用户选择影响权限授予

下表显示了系统根据用户在权限运行时对话框中选择的选项向您的应用授予的权限

精确 大概
仅在使用应用期间 ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
ACCESS_COARSE_LOCATION
仅限本次 ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
ACCESS_COARSE_LOCATION
拒绝 没有位置信息权限 没有位置信息权限

要确定系统授予您的应用哪些权限,请检查您的权限请求的返回值。您可以使用类似以下代码的 Jetpack 库,也可以使用平台库,其中您需要自行管理权限请求代码

Kotlin

@RequiresApi(Build.VERSION_CODES.N)
fun requestPermissions() {
    val locationPermissionRequest = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        when {
            permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
                // Precise location access granted.
            }
            permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
                // Only approximate location access granted.
            }
            else -> {
                // No location access granted.
            }
        }
    }

    // Before you perform the actual permission request, check whether your app
    // already has the permissions, and whether your app needs to show a permission
    // rationale dialog. For more details, see Request permissions:
    // https://developer.android.com/training/permissions/requesting#request-permission
    locationPermissionRequest.launch(
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        )
    )
}

Java

private void requestPermissions() {

    ActivityResultLauncher<String[]> locationPermissionRequest =
            registerForActivityResult(new ActivityResultContracts
                            .RequestMultiplePermissions(), result -> {

                Boolean fineLocationGranted = null;
                Boolean coarseLocationGranted = null;

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    fineLocationGranted = result.getOrDefault(
                            Manifest.permission.ACCESS_FINE_LOCATION, false);
                    coarseLocationGranted = result.getOrDefault(
                            Manifest.permission.ACCESS_COARSE_LOCATION,false);
                }

                if (fineLocationGranted != null && fineLocationGranted) {
                    // Precise location access granted.
                } else if (coarseLocationGranted != null && coarseLocationGranted) {
                    // Only approximate location access granted.
                } else {
                    // No location access granted.
                }
            }
        );

    // ...

    // Before you perform the actual permission request, check whether your app
    // already has the permissions, and whether your app needs to show a permission
    // rationale dialog. For more details, see Request permissions.
    locationPermissionRequest.launch(new String[] {
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    });
}

请求升级到精确位置信息

您可以请求用户将您的应用访问权限从大概位置信息升级到精确位置信息。但是,在请求用户将您的应用访问权限升级到精确位置信息之前,请考虑您的应用的用例是否绝对需要此级别的精度。如果您的应用需要通过蓝牙或 Wi-Fi 与附近的设备配对,请考虑使用伴侣设备配对蓝牙权限,而不是请求 ACCESS_FINE_LOCATION 权限。

要请求用户将您应用的位置信息访问权限从大概升级到精确,请执行以下操作

  1. 如有必要,解释您的应用为何需要该权限
  2. 再次同时请求 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限。由于用户已经允许系统向您的应用授予大概位置信息,此次系统对话框将有所不同,如图 4 和图 5 所示
The dialog contains the options 'Change to precise
         location', 'Only this time', and 'Deny'.
图 4. 用户之前选择的是大概仅在使用应用期间(在图 3 的对话框中)。
The dialog contains the options 'Only this time' and
         'Deny'.
图 5. 用户之前选择的是大概仅限本次(在图 3 的对话框中)。

最初仅请求前台位置信息

即使您的应用中的多个功能需要位置信息访问权限,很可能其中只有一部分需要后台位置信息访问权限。因此,建议您的应用对位置信息权限执行递增请求,先请求前台位置信息访问权限,然后请求后台位置信息访问权限。通过执行递增请求,您可以为用户提供更多控制权和透明度,因为他们可以更好地了解您的应用中哪些功能需要后台位置信息访问权限。

图 6 展示了旨在处理递增请求的应用示例。“显示当前位置”和“推荐附近地点”功能都需要前台位置信息访问权限。但只有“推荐附近地点”功能需要后台位置信息访问权限。

The button that enables foreground location access is
    positioned half a screen length away from the button that enables background
    location
图 6. 这两项功能都需要位置信息访问权限,但只有“推荐附近地点”功能需要后台位置信息访问权限。

执行递增请求的过程如下

  1. 首先,您的应用应引导用户使用需要前台位置信息访问权限的功能,例如图 1 中的“分享位置信息”功能或图 6 中的“显示当前位置”功能。

    建议您禁用需要后台位置信息访问权限的功能的用户访问权限,直到您的应用拥有前台位置信息访问权限为止。

  2. 稍后,当用户探索需要后台位置信息访问权限的功能时,您可以请求后台位置信息访问权限。

更多资源

如需详细了解 Android 中的位置信息权限,请参阅以下资料

Codelabs

视频

示例