Google Play 服务和运行时权限

自 Android 6.0 Marshmallow 起,Android 使用一种权限模型,可简化应用安装和自动更新流程。权限是在运行时请求的,而不是在应用安装之前请求的。此外,用户还可以选择拒绝授予特定权限。 为了让用户能够灵活地管理权限,您需要确保应用在用户启用或停用特定权限时能够按预期运行。

Google Play 服务本身具有运行时权限,用户可以选择拒绝这些权限,而无需拒绝您的应用专门请求的权限。Google Play 服务会自动获取支持其 API 所需的所有权限。不过,您的应用仍应根据需要检查和请求运行时权限,并在用户拒绝向 Google Play 服务授予应用所用 API 所需的权限时,妥善处理错误。

在设置运行时可能需要的权限时,最好管理用户的预期。以下最佳实践有助于您避免潜在问题。

前提条件

您需要在 AndroidManifest.xml 文件中声明权限。例如:

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

指南

在调用 API 之前验证权限

AndroidManifest.xml 文件中声明要使用的 API 后,请先验证您是否拥有所需的权限,然后再调用 API。可以使用 ActivityCompatContextCompatcheckSelfPermission 方法来完成此操作。

如果调用返回 false,则表示权限未获授予,您应使用 requestPermissions 请求权限。此操作的响应会通过回调返回,您将在下一步中看到。

例如:

Kotlin

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
            // Request Permissions Now
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                REQUEST_LOCATION_PERMISSION_CODE)
    } else {
        // permission has been granted, continue as usual
        val locationResult = LocationServices
            .getFusedLocationProviderClient(this /* Context */)
            .lastLocation
    }

Java

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
        // Request Permissions Now
        ActivityCompat.requestPermissions(
            this,
            new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
            REQUEST_LOCATION_PERMISSION_CODE);
    } else {
        // permission has been granted, continue as usual
        Task locationResult = LocationServices
            .getFusedLocationProviderClient(this /** Context */)
            .getLastLocation();
    }

实现请求权限回调

如果用户尚未授予应用所需的权限,则应调用 requestPermissions 方法来请求用户授予这些权限。用户的回答会捕获在 onRequestPermissionsResult 回调中。您的应用应实现此方法,并始终检查返回值,因为请求可能会被拒绝或取消。您还可以一次性请求并检查多项权限,而以下示例仅检查一项权限。

Kotlin

    fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array,
        grantResults: IntArray
    ) {
        if (requestCode == REQUEST_LOCATION_PERMISSION_CODE) {
            if (grantResults.singleOrNull() == PackageManager.PERMISSION_GRANTED) {
               // We can now safely use the API we requested access to
               val locationResult: Task = LocationServices
                    .getFusedLocationProviderClient(this /* Context */)
                    .lastLocation // Request the last known location.
            } else {
                // Permission was denied or request was cancelled
            }
        }
    }

Java

    public void onRequestPermissionsResult(int requestCode,
                                            String[] permissions,
                                            int[] grantResults) {
        if (requestCode == REQUEST_LOCATION_PERMISSION_CODE) {
            if(grantResults.length == 1
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // We can now safely use the API we requested access to
                Task locationResult = LocationServices
                    .getFusedLocationProviderClient(this /** Context */)
                    .getLastLocation(); // Request the last known location.
            } else {
                // Permission was denied or request was cancelled
            }
        }
    }

显示权限使用理由

如果应用请求的权限是应用核心功能所必需的,并且用户之前拒绝了该权限请求,则应用应在再次请求该权限之前显示额外的说明。当用户了解需要某项权限的原因以及授予该权限后可立即获得的好处时,他们更有可能会授予该权限。

在这种情况下,在调用 requestPermissions 之前,您应调用 shouldShowRequestPermissionRationale。如果返回 true,您应创建一些界面来显示权限的其他背景信息。

例如,您的代码可能如下所示:

Kotlin

    private const val REQUEST_LOCATION_PERMISSION_CODE = 2
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
        // Check Permissions Now
        if ActivityCompat.shouldShowRequestPermissionRationale(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) {
            // Display UI and wait for user interaction
        } else {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                REQUEST_LOCATION_PERMISSION_CODE)
        }
    } else {
        // Permission has already been granted, continue as usual
        val locationResult: Task = LocationServices
            .getFusedLocationProviderClient(this /* Context */)
            .lastLocation
    }

Java

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
        // Check Permissions Now
        private static final int REQUEST_LOCATION_PERMISSION_CODE = 2;
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.ACCESS_FINE_LOCATION)) {
            // Display UI and wait for user interaction
        } else {
            ActivityCompat.requestPermissions(
                this, new String[]{Manifest.permission.LOCATION_FINE},
                REQUEST_LOCATION_PERMISSION_CODE);
        }
    } else {
        // permission has been granted, continue as usual
        Task locationResult = LocationServices
            .getFusedLocationProviderClient(this /** Context */)
            .getLastLocation();
    }

Google Play 服务 API 调用会自动显示一个对话框(如果客户端是使用 Activity 实例化的)或系统托盘通知(如果客户端是使用 Context 实例化的),用户可以点按该对话框或通知来启动权限解析 intent。获得相应权限后,系统会将调用加入队列并重试。