所有 Android 應用程式都是在存取權受限的沙箱中執行。如果您的應用程式需要使用自身沙箱以外的資源或資訊,您可以宣告執行階段權限,並設定提供這項存取權的權限要求。這些步驟屬於使用權限工作流程的一環。
如果您宣告任何危險權限,且應用程式安裝在搭載 Android 6.0 (API 級別 23) 以上版本的裝置上,就必須按照本指南所說明的步驟,在執行階段要求危險權限。
如果您未宣告任何危險權限,或者您的應用程式是安裝在搭載 Android 5.1 (API 級別 22) 以下版本的裝置上,則系統會自動授予相關權限,您不需要完成本頁所述的其餘步驟。
基本原則
在執行階段要求權限的基本原則如下:
- 在使用者開始與需要這項權限的功能互動時,要求與情境相關的權限。
- 請不要封鎖使用者,請一律提供取消說明 UI 流程的選項,例如說明要求權限基本原理的流程。
- 如果使用者拒絕或撤銷特定功能所需的權限,請為應用程式進行優雅降級,讓使用者能夠繼續使用應用程式,可能的做法是停用需要相關權限的功能。
- 切勿假設系統行為。舉例來說,不要假設權限都屬於同一個權限群組。權限群組的作用只是協助系統在應用程式要求密切相關的權限時,盡可能減少向使用者顯示的系統對話方塊數量。
要求權限的工作流程
在應用程式中宣告及要求執行階段權限之前,請先評估您的應用程式是否需要這麼做。您不需要宣告任何權限,就可以在應用程式中實現多種用途,例如拍照、暫停媒體播放以及顯示相關廣告。
如果您已確定應用程式需要宣告及要求執行階段權限,請完成下列步驟:
- 在應用程式的資訊清單檔案中,宣告應用程式可能需要要求的權限。
- 設計應用程式專屬的使用者體驗,藉此讓應用程式中的特定動作與特定執行階段權限建立關聯。讓使用者知道哪些動作可能會要求他們授予應用程式存取使用者私人資料的權限。
- 等待使用者在應用程式中叫用需要存取特定使用者私人資料的工作或動作。屆時,您的應用程式就能要求存取這些資料所需的執行階段權限。
檢查使用者是否已授予應用程式所需的執行階段權限。如果是的話,您的應用程式就能存取使用者私人資料。否則,請繼續進行下一個步驟。
每次執行需要該權限的作業時,都必須檢查是否擁有該項權限。
檢查應用程式是否應向使用者顯示原因,說明應用程式需要使用者授予特定執行階段權限的理由。如果系統判定您的應用程式不應顯示原因,請直接繼續進行下一個步驟,不需顯示任何 UI 元素。
不過,要是系統判定應用程式應顯示原因,則請在 UI 元素中向使用者顯示該原因。這個原因應清楚說明應用程式要嘗試存取哪些資料,以及使用者授予執行階段權限後,應用程式能為其提供什麼好處。使用者確認原因後,請繼續進行下一個步驟。
要求執行階段權限,您的應用程式必須取得這項權限才能存取使用者私人資料。系統會顯示執行階段權限提示,例如權限總覽頁面所顯示的提示。
檢查使用者的回覆,看他們選擇授予或拒絕執行階段權限。
如果使用者已授予應用程式權限,您就可以存取使用者私人資料。如果使用者拒絕權限要求,請為應用程式體驗進行優雅降級,這樣一來,即使沒有受到該項權限保護的相關資訊,應用程式也能為使用者執行功能。
圖 1 說明與這項程序相關聯的工作流程和一連串的決策過程:
判斷您的應用程式是否已獲授予相關權限
如要檢查使用者是否已授予應用程式特定權限,請將該權限傳入 ContextCompat.checkSelfPermission()
方法。這個方法會傳回 PERMISSION_GRANTED
或 PERMISSION_DENIED
(視應用程式是否擁有該權限而定)。
說明應用程式需要相關權限的原因
系統在您呼叫 requestPermissions()
時顯示的權限對話方塊會指出應用程式需要的權限,但並未說明原因。在某些情況下,使用者可能會感覺莫名其妙。建議您在呼叫 requestPermissions()
前,先向使用者說明應用程式需要這些權限的原因。
研究顯示,如果知道應用程式需要權限的原因 (例如是否需要權限來支援應用程式的核心功能或廣告),使用者才會更願意接受權限要求。因此,如果您只要使用隸屬特定權限群組的部分 API 呼叫,明確列出您要使用其中的哪些權限以及原因,就非常有用。舉例來說,如果您只需使用概略的位置資訊,請在應用程式說明或應用程式相關說明文章中告知使用者。
在某些情況下,即時告知使用者機密資料的存取情形也是有利的做法。舉例來說,假設您要使用的是相機或麥克風,通常會建議在應用程式的某個位置或通知匣 (如果應用程式是在背景執行) 中顯示通知圖示來告知使用者,以免看起來像在暗中收集資料。
最後,如果您要求權限的目的是讓應用程式的特定功能順利運作,但使用者無法清楚知道原因,請找出方法來告知使用者您需要這些最機密權限的原因。
如果 ContextCompat.checkSelfPermission()
方法傳回 PERMISSION_DENIED
,請呼叫 shouldShowRequestPermissionRationale()
。如果這個方法傳回 true
,請向使用者顯示說明 UI。透過 UI,說明使用者想要啟用的功能需要特定權限的原因。
此外,如果您的應用程式要求位置資訊、麥克風或相機的相關權限,建議您說明應用程式需要存取這些資訊的原因。
要求權限
使用者查看說明 UI 後,或是 shouldShowRequestPermissionRationale()
的傳回值表示您不需要顯示這類 UI,您就可以要求權限。使用者會看到系統權限對話方塊,並且在此選擇是否要將特定權限授予您的應用程式。
方法是使用 AndroidX 程式庫所提供的 RequestPermission
合約,允許系統管理權限要求代碼。由於使用 RequestPermission
合約可以簡化您的邏輯,因此請盡可能採用建議的解決方案。不過,您也可以視需要在權限要求中自行管理要求代碼,並在權限回呼邏輯中加入這個要求代碼。
允許系統管理權限要求代碼
如要讓系統管理與權限要求相關聯的要求代碼,請在模組的 build.gradle
檔案中新增下列程式庫的依附元件:
androidx.activity
1.2.0 以上版本。androidx.fragment
1.3.0 以上版本。
接著,您可以使用下列其中一種類別:
- 如要要求單一權限,請使用
RequestPermission
。 - 如要同時要求多項權限,請使用
RequestMultiplePermissions
。
下列步驟將說明如何使用 RequestPermission
合約,其程序與使用 RequestMultiplePermissions
合約幾乎相同。
在活動或片段的初始化邏輯中,將
ActivityResultCallback
的實作傳入對registerForActivityResult()
的呼叫中。ActivityResultCallback
會定義應用程式如何處理使用者對權限要求的回應。請保留
registerForActivityResult()
(屬於ActivityResultLauncher
類型) 的傳回值當做參考。如需在必要時顯示系統權限對話方塊,請在您在先前步驟儲存的
ActivityResultLauncher
例項上呼叫launch()
方法。呼叫
launch()
後,畫面上隨即顯示系統權限對話方塊。使用者做出選擇後,系統會以非同步方式叫用您在先前步驟中定義的ActivityResultCallback
實作。注意:應用程式「無法」自訂您呼叫
launch()
時顯示的對話方塊。如要為使用者提供更多資訊或相關說明,請變更應用程式 UI,讓使用者更容易瞭解為何應用程式中的某項功能需要特定權限。例如,您可以變更用於啟用這項功能的按鈕文字。此外,系統權限對話方塊會參照與所要求權限相關的權限群組來顯示相關文字。這類權限群組是專為系統簡單易用所設計,因此您的應用程式不應受限於所需權限是否屬於特定權限群組。
下列程式碼片段說明該如何處理權限要求回應:
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 // feature 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 // feature 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. } ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user 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 (ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION)) { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user 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(...) } ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user 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 (ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION)) { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user 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 feature 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 feature 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. } }
要求位置存取權
要求位置存取權時,請遵守與其他執行階段權限相同的最佳做法。就位置存取權而言,系統包括與位置相關的多項權限,此為一項重要的差異。您要求哪些權限以及如何要求這些權限,視應用程式使用狀況適用的位置資訊需求而定。
前景位置資訊
如果您的應用程式包含只會分享或接收位置資訊一次,或只在指定時間內分享位置資訊的功能,則必須取得前景位置資訊存取權才能執行該功能。以下提供幾個範例:
- 使用者可以利用導航應用程式提供的功能,取得即時路線導航。
- 使用者可以利用訊息應用程式提供的功能,將自己目前的位置分享給其他使用者。
如果應用程式的特定功能會在下列任一情況下存取裝置的目前位置,系統就會判定您的應用程式將使用前景位置資訊:
- 屬於應用程式的活動會顯示在畫面上。
您的應用程式正在執行前景服務。執行前景服務時,系統會透過顯示常駐通知來吸引使用者的注意。應用程式會在處於背景時保留存取權,例如當使用者按下裝置上的「主畫面」按鈕或關閉裝置螢幕時。
在 Android 10 (API 級別 29) 以上版本中,您必須宣告
location
的前景服務類型,如以下程式碼片段所示。在舊版 Android 中,建議您宣告此前景服務類型。<!-- 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 go here. --> </service>
應用程式要求 ACCESS_COARSE_LOCATION
權限或 ACCESS_FINE_LOCATION
權限時,您必須宣告應用程式需要使用前景位置資訊,如以下程式碼片段所示:
<manifest ... > <!-- Include this permission any time your app needs location information. --> <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) 開始,如果使用者在裝置上安裝應用程式後不限時間內,不止一次針對特定權限要求輕觸「拒絕」,則當您的應用程式再次要求該權限時,使用者就不會看到系統權限對話方塊。使用者的動作暗示著「不要再詢問」。在先前的版本中,除非使用者之前曾經勾選「不再詢問」核取方塊或選項,否則應用程式每次要求權限時,使用者都會看到系統權限對話方塊。
如果使用者多次拒絕權限要求,系統會將此視為永久拒絕。請務必只在使用者需要存取特定功能時才提示他們授予權限,否則您可能會在不經意間失去重新要求權限的資格。
在某些情況下,系統可能會自動拒絕權限要求,使用者無須採取任何動作 (同樣地,系統也可能自動「授予」權限)。重要的是,不要假設系統會自動執行任何行為。每次應用程式要存取需要具備特定權限的功能時,都應該檢查應用程式是否仍獲授予相關權限。
如要在要求應用程式權限時提供最佳使用者體驗,另請參閱「應用程式權限最佳做法」。
在測試和偵錯時檢查遭拒狀態
使用者可能會永久拒絕授予應用程式權限,如要確認是否如此 (出於偵錯和測試目的),請使用下列指令:
adb shell dumpsys package PACKAGE_NAME
其中 PACKAGE_NAME 是要檢查的套件名稱。
您可以在指令輸出內容中看到類似下方的區段:
... runtime permissions: android.permission.POST_NOTIFICATIONS: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] android.permission.ACCESS_FINE_LOCATION: granted=false, flags=[ USER_SET|USER_FIXED|USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] android.permission.BLUETOOTH_CONNECT: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] ...
遭使用者拒絕一次的權限會標記 USER_SET
。如果是遭使用者選取「拒絕」兩次,表明要永久拒絕的權限,則會標記 USER_FIXED
。
為確保測試人員在測試期間看到要求對話方塊,請在完成應用程式偵錯後重設這些標記。如要執行此操作,請使用下列指令:
adb shell pm clear-permission-flags PACKAGE_NAME PERMISSION_NAME user-set user-fixed
PERMISSION_NAME 是您要重設的權限名稱。
如要查看 Android 應用程式權限的完整清單,請參閱權限 API 參考資料頁面。
單次授權
從 Android 11 (API 級別 30) 開始,每次應用程式要求位置資訊、麥克風或相機的相關權限時,向使用者顯示的權限對話方塊就會包含稱為「僅允許這一次」的選項 (如圖 2 所示)。如果使用者在對話方塊中選取這個選項,您的應用程式就會獲得暫時的單次授權。
接著,您的應用程式可以在一段時間內存取相關資料,這段時間視應用程式的行為和使用者的動作而定:
- 當使用者能看到應用程式的活動時,應用程式可以存取相關資料。
- 如果使用者將應用程式傳送至背景,您的應用程式可以在短時間內繼續存取相關資料。
- 如果您在使用者看到該活動時啟動前景服務,而後使用者將應用程式移至背景,則應用程式可以繼續存取相關資料,直到該前景服務結束為止。
權限撤銷時,應用程式處理程序就會終止
如果使用者撤銷單次授權 (例如透過系統設定撤銷授權),則無論您是否啟動前景服務,應用程式都無法存取相關資料。就像任何權限一樣,只要使用者撤銷應用程式的單次授權,應用程式的程序就會終止。
使用者下次開啟您的應用程式,且應用程式的特定功能要求存取位置資訊、麥克風或相機時,系統會再次提示使用者授予權限。
重設未使用的權限
Android 提供多種將未使用的執行階段權限重設為預設拒絕狀態的方式:
- 這個 API 可讓您主動移除應用程式對於未使用執行階段權限的存取權。
- 自動重設未使用應用程式權限的系統機制。
移除應用程式存取權
在 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
其他資源
如要進一步瞭解權限,請參閱下列文章:
如要進一步瞭解如何要求權限,請參閱權限範例。
您還可以完成這個示範隱私權最佳做法的程式碼研究室。