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
            }
        }
    }

顯示權限說明

如果應用程式要求的權限是核心功能所必需,且使用者先前已拒絕權限要求,應用程式應先顯示額外說明,再重新要求權限。如果使用者瞭解應用程式需要權限的原因,以及授予權限後可立即獲得的好處,就更可能授予權限。

在這種情況下,您應先呼叫 shouldShowRequestPermissionRationale,再呼叫 requestPermissions。如果傳回 true,您應建立一些 UI,顯示權限的其他背景資訊。

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

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 例項化),使用者可以輕觸這些項目來啟動權限解決意圖。通話會排入佇列,並在授予權限後重試。