要求應用程式權限

Stay organized with collections Save and categorize content based on your preferences.

所有 Android 應用程式都是在存取權受限的沙箱中執行。如果您的應用程式需要使用自身沙箱以外的資源或資訊,您可以宣告權限,並設定提供這項存取權的權限要求。這些步驟屬於使用權限工作流程的一環。

如果您宣告任何危險權限,且您的應用程式是安裝在搭載 Android 6.0 (API 級別 23) 以上版本的裝置上,則您必須按照本指南所提供的步驟,在執行階段要求危險權限。

如果您未宣告任何危險權限,或者您的應用程式是安裝在搭載 Android 5.1 (API 級別 22) 以下版本的裝置上,則系統會自動授予相關權限,您不需要完成本頁所述的其餘步驟。

基本原則

在執行階段要求權限的基本原則如下:

  • 在使用者開始與需要這項權限的功能互動時,要求與情境相關的權限。
  • 請不要封鎖使用者,而是針對與權限相關的教育性質 UI 流程,一律提供取消的選項。
  • 如果使用者拒絕或撤銷特定功能所需的權限,請為應用程式進行優雅降級,讓使用者能夠繼續使用應用程式,可能的做法是停用需要相關權限的功能。
  • 切勿假設系統行為。舉例來說,不要假設權限都屬於同一個權限群組。權限群組的作用只是協助系統在應用程式要求密切相關的權限時,盡可能減少向使用者顯示的系統對話方塊數量。

要求權限的工作流程

在應用程式中宣告及要求執行階段權限之前,請先評估您的應用程式是否需要這麼做。您不需要宣告任何權限,就可以在應用程式中實現多種用途,例如拍照、暫停媒體播放以及顯示相關廣告。

如果您已確定應用程式需要宣告及要求執行階段權限,請完成下列步驟:

  1. 在應用程式的資訊清單檔案中,宣告應用程式可能需要要求的權限
  2. 設計應用程式專屬的使用者體驗,藉此讓應用程式中的特定動作與特定執行階段權限建立關聯。使用者應知道哪些動作可能會要求他們授予應用程式存取使用者私人資料的權限。
  3. 等待使用者在應用程式中叫用需要存取特定使用者私人資料的工作或動作。屆時,您的應用程式就能要求存取這些資料所需的執行階段權限。
  4. 檢查使用者是否已授予應用程式所需的執行階段權限。如果是的話,您的應用程式就能存取使用者私人資料。否則,請繼續進行下一個步驟。

    每次執行需要執行階段權限的作業時,您都必須檢查是否擁有該項權限。

  5. 檢查應用程式是否應向使用者顯示原因,說明應用程式需要使用者授予特定執行階段權限的理由。 如果系統判定您的應用程式不應顯示原因,請直接繼續進行下一個步驟,不需顯示任何 UI 元素。

    不過,要是系統判定應用程式應顯示原因,則請在 UI 元素中向使用者顯示該原因。這個原因應清楚說明應用程式要嘗試存取哪些資料,以及使用者授予執行階段權限後,應用程式能提供什麼好處。使用者確認原因後,請繼續進行下一個步驟。

  6. 要求執行階段權限,您的應用程式必須取得這項權限才能存取使用者私人資料。系統會顯示執行階段權限提示,例如權限總覽頁面所顯示的提示。

  7. 檢查使用者的回應,看他們選擇授予或拒絕執行階段權限。

  8. 如果使用者已授予應用程式權限,您就可以存取使用者私人資料。如果使用者拒絕權限要求,請為應用程式體驗進行優雅降級,這樣一來,即使沒有受到該項權限保護的相關資訊,應用程式也能為使用者執行功能。

圖 1 說明與這項程序相關聯的工作流程和一連串的決策過程:

圖 1. 這張圖表顯示了在 Android 上宣告及要求執行階段權限的工作流程。

判斷您的應用程式是否已獲授予相關權限

如要檢查使用者是否已授予應用程式特定權限,請將該權限傳入 ContextCompat.checkSelfPermission() 方法。這個方法會傳回 PERMISSION_GRANTEDPERMISSION_DENIED (視應用程式是否擁有該權限而定)。

說明應用程式需要相關權限的原因

系統在您呼叫 requestPermissions() 時顯示的權限對話方塊會指出應用程式需要的權限,但並未說明原因。在某些情況下,使用者可能會感覺莫名其妙。建議您在呼叫 requestPermissions() 前,先向使用者說明應用程式需要這些權限的原因。

研究顯示,如果知道應用程式需要權限的原因,使用者就更能放心授予權限。使用者研究顯示:

...使用者對特定行動應用程式授予特定權限的意願,受到與此權限相關用途的影響很大。舉例來說,使用者是否願意授予他們的位置資訊存取權,取決於這項要求是否為支援應用程式的核心功能所需,或者是否要與廣告聯播網/數據分析公司分享這項資訊。1

與其他人共同合作這個主題的相關研究後,來自卡內基美隆大學 (Carnegie Mellon University) 的 Jason Hong 教授得出以下結論:

…比起直接告知應用程式將要使用他們的位置資訊,當使用者瞭解應用程式為何要使用他們的位置資訊等這類機密資料 (例如用於放送指定目標的廣告) 時,他們會更加放心。1

因此,如果您只要使用隸屬特定權限群組的部分 API 呼叫,明確列出您要使用其中的哪些權限以及原因,就非常有用。舉例來說:

  • 如果您只需使用概略的位置資訊,請在應用程式說明或應用程式相關說明文章中告知使用者。
  • 如果您需要存取簡訊來接收驗證碼,以避免使用者遭受詐欺,請在應用程式說明中以及應用程式首次需要存取該項資料時,告知使用者。

    注意:如果您的應用程式指定 Android 8.0 (API 級別 26) 以上版本,請不要透過要求 READ_SMS 權限的方式驗證使用者憑證,而是改為使用 createAppSpecificSmsToken() 產生應用程式專屬權杖,然後將這個權杖傳遞給可傳送驗證簡訊的其他應用程式或服務。

在某些情況下,即時告知使用者機密資料的存取情形也是有利的做法。舉例來說,假設您要使用的是相機或麥克風,通常會建議在應用程式的某個位置或通知匣 (如果應用程式是在背景執行) 中顯示通知圖示來告知使用者,以免看起來像在暗中收集資料。

最後,如果您要求權限的目的是讓應用程式的特定功能順利運作,但使用者無法清楚知道原因,請找出方法來告知使用者您需要這些最機密權限的原因。

如果 ContextCompat.checkSelfPermission() 方法傳回 PERMISSION_DENIED,請呼叫 shouldShowRequestPermissionRationale()。如果這個方法傳回 true,請向使用者顯示具教育性質的使用者介面。透過使用者介面,說明使用者想要啟用的功能需要特定權限的原因。

此外,如果您的應用程式要求位置資訊、麥克風或相機的相關權限,建議您說明應用程式需要存取這些資訊的原因

要求權限

使用者查看具教育性質的使用者介面後,或是 shouldShowRequestPermissionRationale() 的傳回值表示您這次不需要顯示這類使用者介面,您就可以要求權限。使用者會看到系統權限對話方塊,並且在此選擇是否要將特定權限授予您的應用程式。

一般來說,您應在權限要求中自行管理要求代碼,並在權限回呼邏輯中加入這組要求代碼。另一種做法是使用 AndroidX 程式庫所提供的 RequestPermission 合約,允許系統在此替您管理權限要求代碼。由於使用 RequestPermission 合約可以簡化您的邏輯,因此建議您盡可能加以運用。

允許系統管理權限要求代碼

如要讓系統管理與權限要求相關聯的要求代碼,請在模組的 build.gradle 檔案中新增下列程式庫的依附元件:

接著,您可以使用下列其中一種類別:

下列步驟將說明如何使用 RequestPermission 合約,其程序與使用 RequestMultiplePermissions 合約幾乎相同。

  1. 在活動或片段的初始化邏輯中,將 ActivityResultCallback 的實作傳入對 registerForActivityResult() 的呼叫中。ActivityResultCallback 會定義應用程式如何處理使用者對權限要求的回應。

    請保留 registerForActivityResult() (屬於 ActivityResultLauncher 類型) 的傳回值當做參考。

  2. 如需在必要時顯示系統權限對話方塊,請在您在先前步驟儲存的 ActivityResultLauncher 例項上呼叫 launch() 方法。

    呼叫 launch() 後,畫面上隨即顯示系統權限對話方塊。使用者做出選擇後,系統會以非同步方式叫用您在先前步驟中定義的 ActivityResultCallback 實作。

    注意:應用程式「無法」自訂您呼叫 launch() 時顯示的對話方塊。如要為使用者提供更多資訊或相關說明,請變更應用程式的使用者介面,讓使用者更容易瞭解為何應用程式中的某項功能需要特定權限。例如,您可以變更用於啟用這項功能的按鈕文字。

    此外,系統權限對話方塊會參照與所要求權限相關的權限群組來顯示相關文字。這類權限群組是專為系統簡單易用所設計,因此您的應用程式不應受限於所需權限是否屬於特定權限群組。

下列程式碼片段說明該如何處理權限要求回應:

Kotlin

// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher. You can use either a val, as shown in this snippet,
// or a lateinit var in your onAttach() or onCreate() method.
val requestPermissionLauncher =
    registerForActivityResult(RequestPermission()
    ) { isGranted: Boolean ->
        if (isGranted) {
            // Permission is granted. Continue the action or workflow in your
            // app.
        } else {
            // Explain to the user that the feature is unavailable because the
            // features requires a permission that the user has denied. At the
            // same time, respect the user's decision. Don't link to system
            // settings in an effort to convince the user to change their
            // decision.
        }
    }

Java

// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher, as an instance variable.
private ActivityResultLauncher<String> requestPermissionLauncher =
    registerForActivityResult(new RequestPermission(), isGranted -> {
        if (isGranted) {
            // Permission is granted. Continue the action or workflow in your
            // app.
        } else {
            // Explain to the user that the feature is unavailable because the
            // features requires a permission that the user has denied. At the
            // same time, respect the user's decision. Don't link to system
            // settings in an effort to convince the user to change their
            // decision.
        }
    });

此外,以下程式碼片段將示範建議的權限檢查程序,並在必要情況下要求使用者授予權限:

Kotlin

when {
    ContextCompat.checkSelfPermission(
            CONTEXT,
            Manifest.permission.REQUESTED_PERMISSION
            ) == PackageManager.PERMISSION_GRANTED -> {
        // You can use the API that requires the permission.
    }
    shouldShowRequestPermissionRationale(...) -> {
        // In an educational UI, explain to the user why your app requires this
        // permission for a specific feature to behave as expected. In this UI,
        // include a "cancel" or "no thanks" button that allows the user to
        // continue using your app without granting the permission.
        showInContextUI(...)
    }
    else -> {
        // You can directly ask for the permission.
        // The registered ActivityResultCallback gets the result of this request.
        requestPermissionLauncher.launch(
                Manifest.permission.REQUESTED_PERMISSION)
    }
}

Java

if (ContextCompat.checkSelfPermission(
        CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
        PackageManager.PERMISSION_GRANTED) {
    // You can use the API that requires the permission.
    performAction(...);
} else if (shouldShowRequestPermissionRationale(...)) {
    // In an educational UI, explain to the user why your app requires this
    // permission for a specific feature to behave as expected. In this UI,
    // include a "cancel" or "no thanks" button that allows the user to
    // continue using your app without granting the permission.
    showInContextUI(...);
} else {
    // You can directly ask for the permission.
    // The registered ActivityResultCallback gets the result of this request.
    requestPermissionLauncher.launch(
            Manifest.permission.REQUESTED_PERMISSION);
}

自行管理權限要求代碼

除了允許系統管理權限要求代碼之外,您也可以自行管理權限要求代碼。如要自行管理,請在呼叫 requestPermissions() 時加入要求代碼。

下列程式碼片段示範如何使用要求代碼要求權限:

Kotlin

when {
    ContextCompat.checkSelfPermission(
            CONTEXT,
            Manifest.permission.REQUESTED_PERMISSION
            ) == PackageManager.PERMISSION_GRANTED -> {
        // You can use the API that requires the permission.
        performAction(...)
    }
    shouldShowRequestPermissionRationale(...) -> {
        // In an educational UI, explain to the user why your app requires this
        // permission for a specific feature to behave as expected. In this UI,
        // include a "cancel" or "no thanks" button that allows the user to
        // continue using your app without granting the permission.
        showInContextUI(...)
    }
    else -> {
        // You can directly ask for the permission.
        requestPermissions(CONTEXT,
                arrayOf(Manifest.permission.REQUESTED_PERMISSION),
                REQUEST_CODE)
    }
}

Java

if (ContextCompat.checkSelfPermission(
        CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
        PackageManager.PERMISSION_GRANTED) {
    // You can use the API that requires the permission.
    performAction(...);
} else if (shouldShowRequestPermissionRationale(...)) {
    // In an educational UI, explain to the user why your app requires this
    // permission for a specific feature to behave as expected. In this UI,
    // include a "cancel" or "no thanks" button that allows the user to
    // continue using your app without granting the permission.
    showInContextUI(...);
} else {
    // You can directly ask for the permission.
    requestPermissions(CONTEXT,
            new String[] { Manifest.permission.REQUESTED_PERMISSION },
            REQUEST_CODE);
}

使用者回應系統權限對話方塊後,系統就會叫用應用程式的 onRequestPermissionsResult() 實作。系統會傳入使用者對權限對話方塊做出的回應,以及您定義的要求代碼,如以下程式碼片段所示:

Kotlin

override fun onRequestPermissionsResult(requestCode: Int,
        permissions: Array<String>, grantResults: IntArray) {
    when (requestCode) {
        PERMISSION_REQUEST_CODE -> {
            // If request is cancelled, the result arrays are empty.
            if ((grantResults.isNotEmpty() &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                // Permission is granted. Continue the action or workflow
                // in your app.
            } else {
                // Explain to the user that the feature is unavailable because
                // the features requires a permission that the user has denied.
                // At the same time, respect the user's decision. Don't link to
                // system settings in an effort to convince the user to change
                // their decision.
            }
            return
        }

        // Add other 'when' lines to check for other
        // permissions this app might request.
        else -> {
            // Ignore all other requests.
        }
    }
}

Java

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
        int[] grantResults) {
    switch (requestCode) {
        case PERMISSION_REQUEST_CODE:
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission is granted. Continue the action or workflow
                // in your app.
            }  else {
                // Explain to the user that the feature is unavailable because
                // the features requires a permission that the user has denied.
                // At the same time, respect the user's decision. Don't link to
                // system settings in an effort to convince the user to change
                // their decision.
            }
            return;
        }
        // Other 'case' lines to check for other
        // permissions this app might request.
    }
}

要求多項權限

要求位置存取權時,請遵循要求任何其他執行階段權限時所採取的相同最佳做法。 就位置存取權而言,系統包括與位置相關的多項權限,此為一項重要的差異。您要求哪些權限以及如何要求這些權限,視應用程式使用狀況適用的位置資訊需求而定。

前景位置資訊

如果您的應用程式包含只會分享或接收位置資訊一次,或只在指定時間內分享位置資訊的功能,則必須取得前景位置資訊存取權才能執行該功能。以下提供幾個範例:

  • 使用者可以利用導航應用程式提供的功能,取得即時路線導航。
  • 使用者可以利用訊息應用程式提供的功能,將自己目前的位置分享給其他使用者。

如果應用程式的特定功能會在下列任一情況下存取裝置的目前位置,系統就會判定您的應用程式將使用前景位置資訊:

  • 屬於應用程式的活動會顯示在畫面上。
  • 您的應用程式正在執行前景服務。執行前景服務時,系統會透過顯示常駐通知來吸引使用者的注意。應用程式會在處於背景時保留存取權,例如當使用者按下裝置上的「主畫面」按鈕或關閉裝置螢幕時。

    此外,建議您宣告前景服務類型location,如以下程式碼片段所示。在 Android 10 (API 級別 29) 以上版本中,您必須宣告此前景服務類型。

    <!-- Recommended for Android 9 (API level 28) and lower. -->
    <!-- Required for Android 10 (API level 29) and higher. -->
    <service
        android:name="MyNavigationService"
        android:foregroundServiceType="location" ... >
        <!-- Any inner elements would go here. -->
    </service>
    

應用程式要求 ACCESS_COARSE_LOCATION 權限或 ACCESS_FINE_LOCATION 權限時,您必須宣告應用程式需要使用前景位置資訊,如以下程式碼片段所示:

<manifest ... >
  <!-- Always include this permission -->
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

  <!-- Include only if your app benefits from precise location access. -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

背景位置資訊

如果應用程式中的特定功能會持續與其他使用者分享位置或使用 Geofencing API,應用程式就需要背景位置資訊存取權。以下提供幾個範例:

  • 使用者可以利用家庭成員位置資訊分享應用程式提供的功能,與家庭成員持續分享位置資訊。
  • 使用者可以利用 IoT 應用程式提供的功能設定家用裝置,讓這些裝置在外出後關閉,並在回家時重新開啟。

只有應用程式在符合前景位置資訊一節所述以外的情況下存取裝置的目前位置時,系統才會判定應用程式將使用背景位置資訊。視應用程式宣告的位置存取權而定,背景位置精確度與前景位置精確度相同。

在 Android 10 (API 級別 29) 以上版本中,您必須在應用程式的資訊清單中宣告 ACCESS_BACKGROUND_LOCATION 權限,才能要求執行階段背景位置存取權。而在舊版 Android 上,當您的應用程式獲得前景位置資訊存取權時,該應用程式也會自動獲得背景位置資訊存取權。

<manifest ... >
  <!-- Required only when requesting background location access on
       Android 10 (API level 29) and higher. -->
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

處理權限要求遭拒的情況

如果使用者拒絕權限要求,您的應用程式應協助使用者瞭解拒絕授權會造成的影響。特別是,您的應用程式應該讓使用者知道,哪些功能會因爲缺少所需權限而無法運作。 進行這項作業時,請務必遵循下列最佳做法:

  • 引導使用者的注意力。 醒目顯示應用程式 UI 中,因為應用程式未擁有必要權限而造成功能受限的特定部分。以下提供幾種可能的做法:

    • 在該功能的結果或資料應該出現的位置顯示相關訊息。
    • 顯示包含錯誤圖示和顏色的不同按鈕。
  • 提供清楚明確的說明,不要顯示一般訊息;而是要說明因應用程式未擁有必要權限而無法使用哪些功能。

  • 不要封鎖使用者介面。也就是說,不要顯示全螢幕的警告訊息,以致使用者完全無法繼續使用您的應用程式。

同時,您的應用程式應尊重使用者拒絕授予權限的決定。從 Android 11 (API 級別 30) 開始,如果使用者在裝置上安裝應用程式後不限時間內,不止一次針對特定權限要求輕觸「拒絕」,則當您的應用程式再次要求該權限時,使用者就不會看到系統權限對話方塊。使用者的動作暗示著「不要再詢問」。在先前的版本中,除非使用者之前曾經勾選「不要再詢問」核取方塊或選項,否則應用程式每次要求權限時,使用者都會看到系統權限對話方塊。

如果使用者多次拒絕權限要求,系統會將此視為永久拒絕。請務必只在使用者需要存取特定功能時才提示他們授予權限,否則您可能會在不經意間失去重新要求權限的資格。

在某些情況下,系統可能會自動拒絕權限要求,使用者無須採取任何動作 (同樣地,系統也可能自動「授予」相關權限)。重要的是,不要假設系統會自動執行任何行為。每次應用程式要存取需要具備特定權限的功能時,您都應該檢查應用程式是否仍獲授予相關權限。

如要在要求應用程式權限時提供最佳使用者體驗,另請參閱「應用程式權限最佳做法」。

單次授權

名為「僅允許這一次」的選項位於對話方塊當中三個按鈕的第二個。
圖 2. 應用程式要求單次授權時顯示的系統對話方塊。

從 Android 11 (API 級別 30) 開始,每次應用程式要求位置資訊、麥克風或相機的相關權限時,向使用者顯示的權限對話方塊就會包含稱為「僅允許這一次」的選項 (如圖 2 所示)。如果使用者在對話方塊中選取這個選項,您的應用程式就會獲得暫時的單次授權

接著,您的應用程式可以在一段時間內存取相關資料,這段時間視應用程式的行為和使用者的動作而定:

  • 當使用者能看到應用程式的活動時,應用程式可以存取相關資料。
  • 如果使用者將應用程式傳送至背景,您的應用程式可以在短時間內繼續存取相關資料。
  • 如果您在使用者看到該活動時啟動前景服務,而後使用者將應用程式移至背景,則應用程式可以繼續存取相關資料,直到該前景服務結束為止。

權限撤銷時,應用程式處理程序就會終止

如果使用者撤銷單次授權 (例如透過系統設定撤銷授權),則無論您是否啟動前景服務,您的應用程式都無法存取相關資料。就像任何權限一樣,只要使用者撤銷應用程式的單次授權,應用程式的程序就會終止。

使用者下次開啟您的應用程式,且應用程式的特定功能要求存取位置資訊、麥克風或相機時,系統會再次提示使用者授予權限。

重設未使用的權限

Android 提供多種將未使用的執行階段權限重設為預設拒絕狀態的方式:

移除應用程式存取權

在 Android 13 (API 級別 33) 以上版本中,您可以移除應用程式不再需要的執行階段權限。在更新應用程式時,執行此步驟,讓使用者更容易瞭解應用程式繼續要求特定權限的原因。這項知識有助於建立使用者對應用程式的信任感。

如要移除執行階段權限的存取權,請將該權限名稱傳入 revokeSelfPermissionOnKill()。如要同時移除一組執行階段權限的存取權,請將權限名稱組合傳遞至 revokeSelfPermissionsOnKill()。權限移除程序會以非同步方式進行,並終止與應用程式 UID 相關聯的所有程序。

為了讓系統移除應用程式的權限存取權,您必須終止所有與應用程式關聯的程序。當您呼叫 API 時,系統會判定在哪些情況下可終止這些程序。系統通常會等待,直到應用程式長時間在背景執行,而不是在前景執行。

如要通知使用者應用程式不再需要存取特定執行階段權限,請在使用者下次啟動應用程式時顯示對話方塊。此對話方塊可能包含權限清單。

針對未使用的應用程式自動重設權限

如果您的應用程式指定的是 Android 11 (API 級別 30) 以上版本,而且未使用的期間長達幾個月,系統會自動重設使用者已授予應用程式的機密執行階段權限,藉此保護使用者資料。詳情請參閱應用程式休眠相關指南。

必要時成為預設處理常式的要求

部分應用程式需要存取通話記錄和簡訊相關的使用者機密資訊。如果想要求通話記錄和簡訊的特定權限,並將應用程式發布至 Play 商店,您必須先提示使用者將應用程式設為特定核心系統函式的預設處理常式,才能要求這些執行階段權限。

如要進一步瞭解預設處理常式,包括如何向使用者顯示預設處理常式提示的指南,請參閱僅用於預設處理常式的權限相關指南

授予所有執行階段權限以便進行測試

如要在模擬器或測試裝置上安裝應用程式時自動授予所有執行階段權限,請使用 adb shell install 指令的 -g 選項,如以下程式碼片段所示:

adb shell install -g PATH_TO_APK_FILE

其他資源

如要進一步瞭解權限,請參閱下列文章:

如要進一步瞭解如何要求權限,請下載下列範例應用程式:

  • Android RuntimePermissionsBasic 範例 Java | Kotlin