Android 6.0 Permission許可權與安全機制,androidpermission
Marshmallow版本許可權修改
android的許可權系統一直是首要的安全概念,因為這些許可權只在安裝的時候被詢問一次。一旦安裝了,app可以在使用者毫不知曉的情況下存取權限內的所有東西,而且一般使用者安裝的時候很少會去仔細看許可權列表,更不會去深入瞭解這些許可權可能帶來的相關危害。但是在android 6.0 Marshmallow版本之後,系統不會在軟體安裝的時候就賦予該app所有其申請的許可權,對於一些危險層級的許可權,app需要在運行時一個一個詢問使用者授予許可權。
舊版本app相容問題
那麼問題來了,是不是所有以前發布的app都會出現問題呢?答案是不會,只有那些targetSdkVersion 設定為23及以上的應用才會出現異常,在使用危險許可權的時候系統必須要獲得使用者的同意才能使用,要不然應用就會崩潰,出現類似下面的錯誤。
java.lang.SecurityException: Permission Denial...
所以targetSdkVersion如果沒有設定為23版本或者以上,系統還是會使用舊規則:在安裝的時候賦予該app所申請的所有許可權。所以app當然可以和以前一樣正常使用了,但是還有一點需要注意的是6.0的系統裡面,使用者可以手動將該app的許可權關閉,在 App info裡面Permissions下邊,可以關閉某個許可權。如果以前的老應用申請的許可權被使用者手動關閉了,不會拋出異常,不會崩潰,只不過調用那些被使用者禁止許可權的api介面傳回值都為null或者0,所以我們只需要做一下判空操作就可以了,這是需要注意的。
普通許可權和危險許可權列表
現在對於新版本的許可權變更應該有了基本的認識,那麼,是不是所有許可權都需要去進行特殊處理呢?當然不是,只有那些危險層級的許可權才需要,可參考官網。
http://developer.android.com/training/permissions/requesting.html
http://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
所以仔細去看看自己的app,對照列表,如果有需要申請其中的一個許可權,就需要進行特殊操作。還有一個比較人性的地方就是如果同一組的任何一個許可權被授權了,其他許可權也自動被授權。例如,一旦WRITE_EXTERNAL_STORAGE被授權了,app也有READ_EXTERNAL_STORAGE許可權了。
支援Marshmallow新版本許可權機制
在Android M的api中,我們可以通過checkSelfPermission檢測軟體是否有某一項許可權,以及使用requestPermissions去請求一組許可權。
int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_FOR_WRITE_PERMISSION); return;}
以上的代碼塊展示了檢測軟體是否有寫檔案的許可權,如果沒有寫檔案的許可權,則通過requestPermissions去向使用者發起請求許可權的流程。向使用者發起請求之後,請求完成,會有相對應的回調方法,通知軟體使用者是否授予了許可權。通過在Activity或者Fragment中重寫onRequestPermissionsResult方法。
@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == CODE_FOR_WRITE_PERMISSION){ if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) && grantResults[0] == PackageManager.PERMISSION_GRANTED){ // 使用者同意寫檔案 } else { // 使用者不同意,自行處理即可 } }}
處理不再提醒
如果使用者一旦拒絕過某許可權的授權。下一次彈框時,使用者會有一個“不再提醒(Never ask again)”的選項的來防止app以後繼續請求授權。
如果這個選項在拒絕授權前被使用者勾選了。下次為這個許可權請求requestPermissions時,對話方塊就不彈出來了,系統會直接回調onRequestPermissionsResult函數,回調結果為最後一次使用者的選擇。所以為了應對這種情況,系統提供了一個shouldShowRequestPermissionRationale()函數,這個函數的作用是協助開發人員找到需要向使用者額外解釋許可權的情況。
注意:第二次請求許可權時,才會有“不再提醒”的選項,如果使用者一直拒絕,並沒有選擇“不再提醒”的選項,下次請求許可權時,會繼續有“不再提醒”的選項,並且shouldShowRequestPermissionRationale()也會一直返回true。
所以利用這個函數我們可以進行相應的最佳化,針對shouldShowRequestPermissionRationale函數返回false的處理有兩種方法:
- 如果應用是第一次請求該許可權,則直接調用requestPermissions函數去請求許可權;如果不是則代表使用者勾選了’不再提醒’,彈出dialog,告訴使用者為什麼你需要該許可權,讓使用者自己手動開啟該許可權。連結:http://stackoverflow.com/questions/32347532/android-m-permissions-confused-on-the-usage-of-shouldshowrequestpermissionrati
- 在onRequestPermissionsResult函數中進行檢測,如果返回PERMISSION_DENIED,則去調用shouldShowRequestPermissionRationale函數,如果返回false代表使用者已經禁止該許可權(上面的3和4兩種情況),彈出dialog告訴使用者你需要該許可權的理由,讓使用者手動開啟。連結:http://stackoverflow.com/questions/30719047/android-m-check-runtime-permission-how-to-determine-if-the-user-checked-nev
處理方法已經有了,修改一下代碼,這裡以第二種方案來處理:
@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == CODE_FOR_WRITE_PERMISSION){ if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) &&grantResults[0] == PackageManager.PERMISSION_GRANTED){ // 使用者同意 } else { // 使用者不同意,向使用者展示該許可權作用 if (!shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { showPermissionDialog();return; } } }}
當勾選不再提醒,並且拒絕之後,彈出dialog,提醒使用者該許可權的重要性
使用相容庫
以上的代碼在6.0版本上使用沒有問題,但是在之前就有問題了,最簡單粗暴的解決方案可能就是利用Build.VERSION.SDK_INT >= 23這個判斷語句來判斷了,方便的是SDK 23的v4包加入了專門類進行相關的處理:
- ContextCompat.checkSelfPermission()被授權函數返回PERMISSION_GRANTED,否則返回PERMISSION_DENIED ,在所有版本都是如此。
- ActivityCompat.requestPermissions()這個方法在6.0之前版本調用,OnRequestPermissionsResultCallback 直接被調用,帶著正確的 PERMISSION_GRANTED或者PERMISSION_DENIED。
- ActivityCompat.shouldShowRequestPermissionRationale()在6.0之前版本調用,永遠返回false。
// 使用相容庫就無需判斷系統版本int hasWriteContactsPermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);if (hasWriteContactsPermission == PackageManager.PERMISSION_GRANTED) { } // 需要彈出dialog讓使用者手動賦予許可權else { ActivityCompat.requestPermissions(Acivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_FOR_WRITE_PERMISSION);}
onRequestPermissionsResult函數不變。後兩個方法,我們也可以在Fragment中使用,用v13相容包:FragmentCompat.requestPermissions() 和 FragmentCompat.shouldShowRequestPermissionRationale()和activity效果一樣。
一次請求多個許可權
當然了有時候需要多個許可權,可以用上面方法一次請求多個許可權。當然最重要的是不要忘了為每個許可權檢查“不再提醒”的設定。
List<String> permissionsNeeded = new ArrayList<String>();permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);permissionsNeeded.add(Manifest.permission.READ_CONTACTS);permissionsNeeded.add(Manifest.permission.WRITE_CONTACTS);requestPermissions(permissionsNeeded.toArray(new String[permissionsList.size()]), CODE_FOR_MULTIPLE_PERMISSION);
最後在onRequestPermissionsResult函數中一個個處理返回結果即可。
第三方庫簡化代碼
當然早就有第三方庫來幫忙做這些事情了:
Github上的開源項目 PermissionHelper,PermissionsDispatcher,EasyPermissions。
APP處於運行狀態下,被撤銷許可權
如果APP正在運行中,使用者進入設定-應用程式頁面去手動撤銷該APP許可權,會出現什麼情況呢?系統又會接著彈出許可權請求對話方塊。
Over
新運行時許可權已經在棉花糖中被使用了。我們沒有退路。我們現在唯一能做的就是保證app適配新許可權模型。欣慰的是只有少數許可權需要運行時許可權模型。大多數常用的許可權,例如,網路訪問,屬於Normal Permission 在安裝時自動會授權,當然你要聲明,以後無需檢查。因此,只有少部分代碼你需要修改。
兩個建議:
1.嚴肅對待新許可權模型。
2.如果你代碼沒支援新許可權,不要設定targetSdkVersion 23 。尤其是當你在Studio建立工程時,不要忘了修改!
說一下代碼修改。這是大事,如果代碼結構被設計的不夠好,你需要一些很蛋疼的重構。每個app都要被修正。如上所說,我們沒的選擇。列出所有你需要請求許可權的全部情形,如果A被授權,B被拒絕,會發生什麼,針對每一個情況認真處理。