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,您應建立一些 UI 來顯示該權限的其他背景資訊。

舉例來說,程式碼可能如下所示:

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 執行個體化),使用者可以輕觸通知來啟動權限解決意圖。取得權限後,系統會將呼叫排入佇列和重試。