Android6.0運作時權限
概述
今天在開發中遇到讀取app緩存檔案的時候,在6.0系統上直接空異常,檢視了manifest配置,其中也加入了相關的權限,于是想到Android 6.0的新特性。
Android 6.0在我們原有的AndroidManifest.xml聲明權限的基礎上,
又新增了運作時權限動态檢測,以下權限都需要在運作時判斷:
身體傳感器
- 月曆
- 攝像頭
- 通訊錄
- 地理位置
- 麥克風
- 電話
- 短信
- 存儲空間
對于6.0的幾個主要的變化,檢視檢視官網的這篇文章http://developer.android.com/intl/zh-cn/about/versions/marshmallow/android-6.0-changes.html,其中也包含Runtime Permissions
官網權限相關文章:
Working with System Permissions
Permissions Best Practices
https://developer.android.com/preview/features/runtime-permissions.html
https://developer.android.com/preview/features/runtime-permissions.html#support-lib
https://developer.android.com/preview/features/runtime-permissions.html#normal
https://developer.android.com/reference/android/content/pm/PermissionInfo.html#PROTECTION_NORMAL
問題及特點
-
運作在android M時才運作時權限
新的運作時權限僅當我們設定targetSdkVersion to 23(這意味着你已經在23上測試通過了)才起作用,當然還要是M系統的手機。app在6.0之前的裝置依然使用舊的權限系統。
-
分組對我們的權限機制有什麼影響嗎?
通過adb shell pm list permissions -d -g進行權限的檢視。
看到上面的dangerous permissions,會發現一個問題,好像危險權限都是一組一組的
如果app運作在Android 6.x的機器上,對于授權機制是這樣的。如果你申請某個危險的權限,假設你的app早已被使用者授權了同一組的某個危險權限,那麼系統會立即授權,而不需要使用者去點選授權。比如你的app對READ_CONTACTS已經授權了,當你的app申請WRITE_CONTACTS時,系統會直接授權通過。此外,對于申請時彈出的dialog上面的文本說明也是對整個權限組的說明,而不是單個權限(ps:這個dialog是不能進行定制的)。
不過需要注意的是,不要對權限組過多的依賴,盡可能對每個危險權限都進行正常流程的申請,因為在後期的版本中這個權限組可能會産生變化。
-
targetSdkVersion 低于 23在android 6.0上運作會有這個權限問題嗎?
如果app的targetSdkVersion 低于 23,那将被認為app沒有用23新權限測試過,那将被繼續使用舊有規則:使用者在安裝的時候不得不接受所有權限,安裝後app就有了那些權限咯!然後app像以前一樣奔跑!注意,此時使用者依然可以取消已經同意的授權!使用者取消授權時,android 6.0系統會警告,但這不妨礙使用者取消授權。
問題又來了,這時候你的app崩潰嗎?
善意的主把這事也告訴了android小組,當我們在targetSdkVersion 低于23的app調用一個需要權限的函數時,這個權限如果被使用者取消授權了的話,不抛出異常。但是他将啥都不幹,結果導緻函數傳回值是null或者0.
-
釋出時注意
代碼沒有成功改為支援最新運作時權限的app,不要設定targetSdkVersion 23 釋出,否則你就有麻煩了。隻有當你測試過了,再改為targetSdkVersion 23 。
警告:現在你在android studio建立項目,targetSdkVersion 會自動設定為 23。如果你還沒支援新運作時權限,我建議你首先把targetSdkVersion 降級到22
運作時權限擷取
1.在AndroidManifest檔案中添加需要的權限。
這個步驟和我們之前的開發并沒有什麼變化,試圖去申請一個沒有聲明的權限可能會導緻程式崩潰
2.檢查權限
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
}else{
//
}
ContextCompat.checkSelfPermission,主要用于檢測某個權限是否已經被授予,方法傳回值為PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。當傳回DENIED就需要進行申請授權了
3.申請授權
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
該方法是異步的,第一個參數是Context;第二個參數是需要申請的權限的字元串數組;第三個參數為requestCode,主要用于回調的時候檢測。可以從方法名requestPermissions以及第二個參數看出,是支援一次性申請多個權限的,系統會通過對話框逐一詢問使用者是否授權。
4.處理權限申請回調
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length >
&& grantResults[] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
}
}
對于權限的申請結果,首先驗證requestCode定位到你的申請,然後驗證grantResults對應于申請的結果,這裡的數組對應于申請時的第二個權限字元串數組。如果你同時申請兩個權限,那麼grantResults的length就為2,分别記錄你兩個權限的申請結果。
5.申請權限的解釋
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS))
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
}
這個API主要用于給使用者一個申請權限的解釋,該方法隻有在使用者在上一次已經拒絕過你的這個權限申請。也就是說,使用者已經拒絕一次了,你又彈個授權框,你需要給使用者一個解釋,為什麼要授權,則使用該方法。
到此,常用的方法是:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
處理 “不再提醒”
如果使用者拒絕某授權。下一次彈框,使用者會有一個“不再提醒”的選項的來防止app以後繼續請求授權。如果這個選項在拒絕授權前被使用者勾選了。下次為這個權限請求requestPermissions時,對話框就不彈出來了,結果就是,app啥都不幹。這将是很差的使用者體驗,使用者做了操作卻得不到響應。這種情況需要好好處理一下。在請求requestPermissions前,我們需要檢查是否需要展示請求權限的提示通過activity的shouldShowRequestPermissionRationale,代碼如下:
final private int REQUEST_CODE_ASK_PERMISSIONS = ;
private void insertDummyContactWrapper() {
int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
//TODO
if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {
showMessageOKCancel("You need to allow access to Contacts",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
}
});
return;
}
requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
return;
//TODO
}
...;
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
後記
權限處理并不複雜,但是需要編寫很多重複的代碼,在github上面的幾個庫:
https://github.com/hotchemi/PermissionsDispatcher
https://github.com/lovedise/PermissionGen
https://github.com/hongyangAndroid/MPermissions.(推薦)
實作明天理想的唯一障礙是今天的疑慮, tks