1. Android 6.0 在運行時請求權限介紹
從 Android 6.0(API 級別 23)開始,用戶開始在應用運行時向其授予權限,而不是在應用安裝時授予。此方法可以簡化應用安裝過程,因為用戶在安裝或更新應用時不需要授予權限。它還讓用戶可以對應用的功能進行更多控制;例如,用戶可以選擇為相機應用提供相機訪問權限,而不提供設備位置的訪問權限。用戶可以隨時進入應用的設置頁面修改權限。
1.1、為什么需要運行時請求權限
iPhone上的App都是默認下載安裝的,然后運行App時需要什么權限就彈窗向用戶申請,這對用戶來說就非常好。因為用戶不想給App權限就不給,而Android 6.0以前是這樣的,我下載了一個App安裝,系統就彈出這個App需要使用的全部的權限,就給我看一下,我需要這個App 的話,只能同意所有的權限都給這個App,要么我不安裝這個App。
1.2、 Android權限介紹
系統權限分為兩類:正常權限和危險權限:
正常權限不會直接給用戶隱私權帶來風險。如果您的應用在其manifest中列出了正常權限,系統將自動授予該權限。
危險權限會授予應用訪問用戶機密數據的權限。如果您的應用在其manifest中列出了正常權限,系統將自動授予該權限。如果您列出了危險權限,則用戶必須明確批準您的應用使用這些權限,也就是說manifest文件中定義的危險權限將不會隨著安裝自動授予。
表 1.危險權限和權限組。
權限組權限
CALENDAR :? READ_CALENDAR , WRITE_CALENDAR
CONTACTS? :? READ_CONTACTS? ,? WRITE_CONTACTS? ,? GET_ACCOUNTS
LOCATION :? ACCESS_FINE_LOCATION? ,? ACCESS_COARSE_LOCATION
PHONE? ? READ_PHONE_STATE,CALL_PHONE,READ_CALL_LOG, WRITE_CALL_LOG,ADD_VOICEMAIL,USE_SIP,PROCESS_OUTGOING_CALLS
SENSORS? :? BODY_SENSORS
SMS? :? SEND_SMS,RECEIVE_SMS,READ_SMS,RECEIVE_WAP_PUSH,RECEIVE_MMS
STORAGE? :? READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
從上圖中我們可以看到,Android系統把危險權限分了9大組,這樣也是為了簡化權限的申請機制。如果你申請了android.permission.READ_CONTACTS讀取聯系人的權限,那么6.0 系統就會把這一組中其他的權限也打包給你。我覺得這個和iOS的隱私管理機制非常相似,在iOS系統設置的“隱私->通訊錄”中可以看到,如果你給一個App通訊錄的權限,那么這個App既可以讀也可以寫的
Android 6.0里面只有危險權限才需要運行時獲取的
1.3、android系統對權限的處理場景分析
如果設備運行的是 Android 6.0(API 級別 23)或更高版本,并且應用的targetSdkVersion是 23 或更高版本,則應用在運行時向用戶請求權限。用戶可隨時調用權限,因此應用在每次運行時均需檢查自身是否具備所需的權限。
如果設備運行的是 Android 5.1(API 級別 22)或更低版本,并且應用的targetSdkVersion是 22 或更低版本,則系統會在用戶安裝應用時要求用戶授予權限。如果將新權限添加到更新的應用版本,系統會在用戶更新應用時要求授予該權限。用戶一旦安裝應用,他們撤銷權限的唯一方式是卸載應用。
如果設備運行的是 Android 6.0(API 級別 23)或更高版本,并且應用的targetSdkVersion是 22 或更低版本,此時Android系統會把你申請的全部權限都給你。用戶依然可以進入App的設置界面把權限關閉!
例如以下圖片中用戶在android6.0的版本的設置中把權限關閉,此時你的權限就用不了了。那么程序需要考慮對6.0及以上版本的兼容,具體參考下面的(android.support.v4.content.PermissionChecker)。
值得注意的是Android系統有一套自動權限調整的機制,我們知道android每次sdk升級有可能會加入新的權限,而你的app已經發布到用戶手機上安裝了,除了升級不可能修改Androidmanifest文件了,此時你可能擔心自己的app能夠在這些新的sdk版本的手機上運行正常嗎,其實android已經考慮了這種場景,Android 將根據為targetSdkVersion屬性提供的值決定應用是否需要權限。如果該值低于在其中添加權限的版本,則 Android 會為App自動添加該權限。
例如,API 級別 4 中加入了WRITE_EXTERNAL_STORAGE權限,用以限制訪問共享存儲空間。如果您的targetSdkVersion為 3 或更低版本,則會向更新 Android 版本設備上的應用添加此權限。
2、如何申請權限
申請權限過程包括以下幾個步驟:
檢查權限
請求權限
處理權限請求響應
解釋應用為什么需要權限
2.1、檢查權限
如果您的應用需要危險權限,則每次執行需要這一權限的操作時您都必須檢查自己是否具有該權限。用戶始終可以自由調用此權限,因此,即使應用昨天使用了相機,它不能假設自己今天仍具有該權限,因為用戶可能在設置里面關閉了。
要檢查您是否具有某項權限,請調用ContextCompat.checkSelfPermission()方法。例如,以下代碼段顯示了如何檢查 Activity 是否具有在日歷中進行寫入的權限:
// 假設thisActivity是當前屏幕最前端正在和用戶交互的activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
? ? ? ? Manifest.permission.WRITE_CALENDAR);
如果應用具有此權限,方法將返回PackageManager.PERMISSION_GRANTED,并且應用可以繼續操作。如果應用不具有此權限,方法將返回PERMISSION_DENIED,且應用必須明確向用戶要求權限。
也可以使用ActivityCompat,它們兩個的checkSelfPermission方法是同一個,因為ActivityCompat繼承自ContextCompat,而checkSelfPermission是ContextCompat中的方法。
檢查權限會有一些特別的問題需要注意,主要有以下兩個:
如果App的targetSdkVersion小于23并且運行在Android 6.0系統上,怎么去檢測用戶關閉了權限呢?
國內有些手機廠商自己定制了手機權限管理(例如:小米),普通的checkSelfPermission已經不準確了,該如何處理?
問題一:
android.support.v4.content.PermissionChecker可以幫我們解決這個問題。這個類的文檔是這么描述的:
For apps targeting API lower thanandroid.os.Build.VERSION_CODES.Mthese permissions are always granted as such apps do not expect permission revocations and would crash. Therefore, when the user disables a permission for a legacy app in the UI the platform disables the APIs guarded by this permission making them a no-op which is doing nothing or returning an empty result or default error.
翻譯一下是:
當app的targetsdkversion小于23的時候,這些權限默認都會自動給當前app,但如果app沒有考慮在6.0設備中被用戶主動撤銷該權限的場景,那么可能造成app的崩潰。于是app在使用該權限過程中系統權限檢查時如果這個權限被用戶撤銷了,那么對應請求的API會什么都不做或者返回一個空的結果,或者出錯。
PermissionChecker.checkSelfPermission方法就是用于檢查App自身有沒有某一個權限,這個方法的返回結果只有三種:
PERMISSION_GRANTED: 已授權
PERMISSION_DENIED: 沒有被授權
PERMISSION_DENIED_APP_OP: 沒有被授權
PERMISSION_DENIED和PERMISSION_DENIED_APP_OP都表示沒有被授權,但是它們的區別就在于targetSdkVersion的值,如果targetSdkVersion小于23,就返回PERMISSION_DENIED_APP_OP,否則就返回PERMISSION_DENIED
因此,如果你的App的targetSdkVersion小于23,但是運行在Android 6.0及以后的系統上,你可以用下面的代碼來檢測app是否有某個權限:
PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker. PERMISSION_DENIED_APP_OP
問題二:
國產很多手機在google之前已經做了自己的權限管理,例如小米,所以此時使用ContextCompat的checkSelfPermission方法即便返回PackageManager.PERMISSION_GRANTED 也可能不準確。如果出現這種情況我們需要做一次特殊處理,此時我們需要用到android的隱藏API --AppOpsManager
AppOpsManager官方的解釋是系統內部使用,不提供給APP開發者使用。一個小米設備兼容判斷的代碼如下:
@TargetApi(Build.VERSION_CODES.M)private static boolean hasSelfPermissionForXiaomi(Context context, String permission) {? ? AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);? ? String op = AppOpsManager.permissionToOp(permission);? ? if (!TextUtils.isEmpty(op)) {? ? ? ? int checkOp = appOpsManager.checkOp(op, Process.myUid(), context.getPackageName());? ? ? ? return checkOp == AppOpsManager.MODE_ALLOWED && ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;? ? }? ? return true;}
2.2、請求權限
如果您的應用需要應用manifest文件中列出的危險權限,那么,它必須要求用戶授予該權限。Android 為您提供了多種權限請求方式。調用這些方法將顯示一個標準的 Android 對話框,不過,您不能對它們進行自定義。
如果上面的權限檢查步驟中結果是應用尚無所需的權限,則應用必須調用一個requestPermissions()方法,以請求適當的權限。應用將傳遞其所需的權限,以及您指定用于識別此權限請求的整型請求代碼。此方法異步運行:它會立即返回,并且在用戶響應對話框之后,系統會使用結果調用應用的回調方法,將應用傳遞的相同請求代碼傳遞到requestPermissions()。
提示用戶授予或拒絕權限的系統對話框如下:
以下代碼可以檢查應用是否具備讀取用戶聯系人的權限,并根據需要請求該權限:
// 這里的 thisActivity 是當前屏幕最前端正在和用戶交互的activity
if (ContextCompat.checkSelfPermission(thisActivity,
? ? ? ? ? ? ? ? Manifest.permission.READ_CONTACTS)
? ? ? ? != PackageManager.PERMISSION_GRANTED) {
? ? // 是否需要給用戶一個解釋?
? ? if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
? ? ? ? ? ? Manifest.permission.READ_CONTACTS)) {
? ? ? ? // 顯示給用戶需要這個權限的理由,這個需要是異步的(不能阻塞當前線程去等待用戶的響應!) ,在用戶看完這個解釋后,繼續嘗試請求這些權限
? ? } else {
? ? ? ? // 不需要解釋, 我們可以開始請求權限
? ? ? ? ActivityCompat.requestPermissions(thisActivity,
? ? ? ? ? ? ? ? new String[]{Manifest.permission.READ_CONTACTS},
? ? ? ? ? ? ? ? MY_PERMISSIONS_REQUEST_READ_CONTACTS);
? ? ? ? // MY_PERMISSIONS_REQUEST_READ_CONTACTS 這個是app定義的整形常量,用于標識一個請求,回調方法中會獲得這個請求對應的結果
? ? }
}
注:當您的應用調用requestPermissions()時,系統將向用戶顯示一個標準對話框。您的應用無法配置或更改此對話框。如果您需要為用戶提供任何信息或解釋,您應在調用requestPermissions()之前進行,如解釋應用為什么需要權限。
2.3、處理權限請求響應
當應用請求權限時,系統將向用戶顯示一個對話框。當用戶響應時,系統將調用應用的onRequestPermissionsResult()方法,向其傳遞用戶響應。您的應用必須覆寫該方法,以了解是否已獲得相應權限。回調會將您傳遞的相同請求代碼傳遞給requestPermissions()。例如,如果應用請求READ_CONTACTS訪問權限,則它可能采用以下回調方法:
@Override
public void onRequestPermissionsResult(int requestCode,
? ? ? ? String permissions[], int[] grantResults) {
? ? switch (requestCode) {
? ? ? ? case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
? ? ? ? ? ? // 如果授權取消 這個結果數組是空
? ? ? ? ? ? if (grantResults.length > 0
? ? ? ? ? ? ? ? && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
? ? ? ? ? ? ? ? // 權限已經授權, 很棒!可以繼續聯系人相關的操作了
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? // 權限被拒絕了,很糟糕! 禁用和該權限相關的功能
? ? ? ? ? ? }
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // 其它的'case' 代碼去處理其它的權限請求回調
? ? }
}
注意:
當系統要求用戶授予權限時,用戶可以選擇指示系統不再要求提供該權限(即勾選對話框里的不在提示)。這種情況下,無論應用在什么時候使用requestPermissions()再次要求該權限,系統都會立即拒絕此請求。系統會調用您的onRequestPermissionsResult()回調方法,并傳遞PERMISSION_DENIED
2.4 解釋應用為什么需要權限
在某些情況下,您可能需要幫助用戶了解您的應用為什么需要某項權限。例如,如果用戶啟動一個攝影應用,用戶對應用要求使用相機的權限可能不會感到吃驚,但用戶可能無法理解為什么此應用想要訪問用戶的位置或聯系人。在請求權限之前,不妨為用戶提供一個解釋。請記住,您不需要通過解釋來說服用戶;如果您提供太多解釋,用戶可能發現應用令人失望并將其移除。
您可以采用的一個方法是僅在用戶已拒絕某項權限請求時提供解釋。如果用戶繼續嘗試使用需要某項權限的功能,但繼續拒絕權限請求,則可能表明用戶不理解應用為什么需要此權限才能提供相關功能。對于這種情況,比較好的做法是顯示解釋。
為了幫助查找用戶可能需要解釋的情形,Android 提供了一個實用程序方法,即shouldShowRequestPermissionRationale()。如果應用之前請求過此權限但用戶拒絕了請求,此方法將返回true。
注:如果用戶在過去拒絕了權限請求,并在權限請求系統對話框中選擇了Don't ask again選項,此方法將返回false。如果設備規范禁止應用具有該權限,此方法也會返回false。
That's all 感謝閱讀,原文地址http://www.huahuaxie.com/android-6-0-runtime-permission/