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 请求这些权限。对此的响应中会返回一个回调中,您将在下一步中看到该回调。

例如:

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

实现请求权限回调

如果用户尚未授予应用所需的权限,应调用 requestPermissions 方法要求用户授予该权限。系统会在 onRequestPermissionsResult 回调中捕获用户的响应。您的应用应实现此功能,并始终检查返回值,因为请求可能会被拒绝或取消。您也可以一次请求和检查多项权限。以下示例仅检查单项权限。

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

显示权限理由

如果您的应用请求的权限对于应用的核心功能来说是必要的,并且用户之前已拒绝该权限请求,那么您的应用在再次请求权限之前应该显示额外的说明。当用户了解需要某项权限的原因以及能为他们带来直接好处时,他们授予相关权限的可能性会更大。

在这种情况下,您应在调用 requestPermissions 之前调用 shouldShowRequestPermissionRationale。如果它返回 true,您应该创建一些界面以显示相应权限的其他上下文。

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

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
    != PackageManager.PERMISSION_GRANTED) {
    // Check Permissions Now
    private static final int REQUEST_LOCATION = 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},
            ACCESS_FINE_LOCATION);
    }
} else {
    // permission has been granted, continue as usual
    Task<Location> locationResult = LocationServices
        .getFusedLocationProviderClient(this /** Context */)
        .getLastLocation();
}

处理连接失败

如果您的应用使用的是已废弃的 GoogleApiClient,当您调用 connect() 时,Google Play 服务会验证它是否具有所需的所有必要权限。如果缺少 Google Play 服务本身所需的任何权限组,connect() 会失败。

如果对 connect() 的调用失败,请确保您的应用能正确处理连接失败。如果 Google Play 服务本身缺少权限,您可以调用 startResolutionForResult() 来启动用户流来修复问题。

例如:

@Override
public void onConnectionFailed(ConnectionResult result) {
    if (mResolvingError) {
        // Already attempting to resolve an error.
        return;
    } else if (result.hasResolution()) {
        try {
            mResolvingError = true;
            result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
        } catch (SendIntentException e) {
            // There was an error with the resolution intent. Try again.
            mGoogleApiClient.connect();
        }
    } else {
        // Show dialog using GooglePlayServicesUtil.getErrorDialog()
        showErrorDialog(result.getErrorCode());
        mResolvingError = true;
    }
}

基于 GoogleApi 的较新 API 调用将自动显示一个对话框(如果客户端使用 Activity 进行实例化)或系统任务栏通知(如果客户端使用 Context 实例化),用户可通过点按该对话框来启动权限解析 intent。授予权限后,调用将加入队列并重试。