問題來源于最近的一個(gè)項(xiàng)目中淌实,原本好好的程序睡汹,在一臺(tái)Nexus 6(Android6.0)上測(cè)試發(fā)現(xiàn)所有需要保存圖片到本地的都不行,看報(bào)錯(cuò):
其實(shí)不僅僅是保存圖片這里報(bào)這個(gè)錯(cuò),還有打開相機(jī)的時(shí)候也會(huì)報(bào)錯(cuò)拢切。好了問題就這么開始了!
看到這里都知道是權(quán)限的問題所引起的原因啃奴,對(duì)扯旷,確實(shí)是因?yàn)闄?quán)限的問題引起的,但是程序明明已經(jīng)在Manifest中聲明了以上操作需要的權(quán)限培己,而且在以前測(cè)試的系統(tǒng)機(jī)型中都是沒問題的碳蛋,為什么!突然間想到了Marshmallow發(fā)布的時(shí)候提及到的Requesting Permissions at Run Time這東西省咨,對(duì)就是他的原因肃弟!
Requesting Permissions at Run Time究竟是啥,官方對(duì)他是如下介紹滴:
從Android6.0(API >= 23)開始零蓉,用戶在APP運(yùn)行的時(shí)候授予其權(quán)限而不是像以前安裝的時(shí)候就通通授予了(以前授權(quán)方式好像沒什么卵用)笤受。由于不在需要在安裝或更新APP的時(shí)候授予相關(guān)權(quán)限,這就簡化了APP的安裝過程敌蜂。這也提高了用戶對(duì)APP功能的控制箩兽,比如:用戶可以選擇讓一個(gè)Camera APP使用Camera,用戶可以在任何時(shí)候在設(shè)置面板撤銷這個(gè)權(quán)限章喉。汗贫。。
看完是不是有點(diǎn)像我們?cè)趪a(chǎn)ROM中常見到的每個(gè)應(yīng)用運(yùn)行時(shí)權(quán)限授予秸脱。
系統(tǒng)權(quán)限也被分成normal
和dangerous
兩類:
- Normal類的權(quán)限不會(huì)直接涉及到用戶隱私風(fēng)險(xiǎn)落包。如果APP在Manifest文件中聲明了Normal類的權(quán)限,系統(tǒng)會(huì)自動(dòng)授予這些權(quán)限摊唇。
-
Dangerous類的權(quán)限可能會(huì)讓APP涉及到用戶機(jī)密的數(shù)據(jù)咐蝇。如果APP在Manifest文件中聲明了Normal類的權(quán)限,系統(tǒng)會(huì)自動(dòng)授予這些權(quán)限巷查。如果在Manifest文件中添加了Dangerous類的權(quán)限有序,用戶必須明確的授予對(duì)應(yīng)的權(quán)限后APP才具有這些權(quán)限撮竿。
關(guān)于哪些權(quán)限屬于Normal類,哪些屬于Dangerous類笔呀,如下圖:
normal-dangerous-permissions.png
更詳細(xì)請(qǐng)看normal-dangerous-permissions文檔。
現(xiàn)在我們知道了在APP中normal和dangerous類的權(quán)限都需要Manifest文件中聲明髓需,但是在不同的版本系統(tǒng)和target SDK效果是不一樣的许师,有如下幾點(diǎn)需要注意:
- 系統(tǒng)Android 5.1以下或target SDK 22以下,只要在Manifest文件中聲明了需要的權(quán)限僚匆,用戶在安裝APP的時(shí)候就會(huì)授予相應(yīng)的權(quán)限微渠;如果不授予當(dāng)然APP也無法安裝。
- 如果設(shè)備運(yùn)行在6.0以上并且你的應(yīng)用的target SDK版本>=23咧擂,APP除了需要在Manifest文件中聲明相應(yīng)的權(quán)限之外逞盆,還要在APP運(yùn)行時(shí)向用戶進(jìn)行請(qǐng)求每個(gè)dangerous類的權(quán)限。用戶可以選擇授予或不授予該權(quán)限松申,即使用戶不授予該權(quán)限APP也可以繼續(xù)運(yùn)行云芦,但是相關(guān)的需要權(quán)限的操作是沒法進(jìn)行的。
使用系統(tǒng)提供的API檢查并請(qǐng)求權(quán)限
a. 檢查權(quán)限
以為用戶可以隨時(shí)撤銷對(duì)APP的授權(quán)贸桶,所以在每次準(zhǔn)備進(jìn)行需要dangerous類權(quán)限操作的時(shí)候舅逸,需要檢查APP是否具有對(duì)應(yīng)的權(quán)限。使用[ContextCompat.checkSelfPermission()](http://developer.android.com/reference/android/support/v4/content/ContextCompat.html#checkSelfPermission(android.content.Context, java.lang.String))檢查權(quán)限皇筛,代碼如下:
// 檢查activity是否有寫日程的權(quán)限
// Assume thisActivity is the current activity
intpermissionCheck=ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.WRITE_CALENDAR);
上面代碼琉历,如果APP有該權(quán)限返回PackageManager.PERMISSION_GRANTED,APP接著可以進(jìn)行對(duì)應(yīng)操作水醋;如果沒有權(quán)限旗笔,以上方法返回PERMISSION_DENIED,APP需要明確的向用戶請(qǐng)求授權(quán)拄踪。
b. 請(qǐng)求權(quán)限
如何使用系統(tǒng)的API向用戶請(qǐng)求dangerous類的權(quán)限蝇恶,也很簡單,support-library都基本上幫我們做好了惶桐。首先這個(gè)權(quán)限已經(jīng)在Manifest文件中聲明(廢話么)艘包,然后向用戶請(qǐng)求該權(quán)限,同意了你就用耀盗,否則想都別想(用戶是上帝)想虎。Google是這么解釋why the app needs permissions的:
在某些情況下,你可能想讓用戶理解為啥你的APP需要這個(gè)權(quán)限叛拷。比如用戶打開了一個(gè)拍攝APP(好吧舌厨,又是Camera),用戶不會(huì)對(duì)這個(gè)APP需要使用Camera感到奇怪忿薇,但是用戶可能不能理解為什么這破APP還需要使用我的位置和聯(lián)系人數(shù)據(jù)(不能忍了)裙椭!所以在你請(qǐng)求某個(gè)權(quán)限之前躏哩,你應(yīng)該考慮提供一段解釋性的話給用戶。請(qǐng)記住你不是要靠這點(diǎn)解釋性的話就讓用戶徹底的明白你為什么需要這個(gè)權(quán)限揉燃,如果你解釋過多扫尺,用戶覺得沒卵用直接卸了APP。只有當(dāng)用戶拒絕了你權(quán)限請(qǐng)求之后才是使用那段解釋性的話最好的時(shí)候炊汤,因?yàn)槿绻脩粢恢眹L試使用某個(gè)需要權(quán)限的功能正驻,但是卻又一直不授予該權(quán)限,這就表明用戶真不知道為什么這個(gè)功能需要這個(gè)這個(gè)權(quán)限抢腐,在這種情況下姑曙,你的解釋性的話就派上用場(chǎng)了。
Android提供[shouldShowRequestPermissionRationale()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String))方法求向用戶展示為啥你需要這個(gè)權(quán)限迈倍,當(dāng)用戶之前已經(jīng)請(qǐng)求過該權(quán)限并且拒絕了授權(quán)這個(gè)方法返回true伤靠。
注意:如果用戶拒絕權(quán)限請(qǐng)求的時(shí)候選擇了Don’t ask again選項(xiàng),上面的方法返回false啼染,當(dāng)然如果設(shè)備本身就不允許有這個(gè)權(quán)限也是返回false宴合。
看看代碼:
// 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,newString[{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.
}
}
其中[requestPermissions()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))就是請(qǐng)求權(quán)限方法,異步方法迹鹅⌒畏模看看這個(gè)方法需要三個(gè)參數(shù):
@param activity The target activity
@param permissions The requested permissions(這里就是你需要申請(qǐng)的權(quán)限,在Manifest類中可以找到你需要的權(quán)限)
@param requestCode Application specific request code to match with a result reported to onRequestPermissionsResult(int, String[], int[])}(標(biāo)志這次授權(quán)的唯一請(qǐng)求碼徒欣,當(dāng)用戶進(jìn)行授權(quán)操作后在回調(diào)方法中可以根據(jù)這個(gè)標(biāo)識(shí)符進(jìn)行區(qū)分不同的授權(quán)操作)
有個(gè)缺點(diǎn)就是使用系統(tǒng)請(qǐng)求授權(quán)API你不能自定義樣式逐样,請(qǐng)求授權(quán)彈出來的是標(biāo)準(zhǔn)的Android Dialog(如下圖,遵循Android標(biāo)準(zhǔn)蠻好的)打肝,如果你希望提供一些信息之類的應(yīng)該在[requestPermissions()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))之前進(jìn)行操作脂新。
c. 處理授權(quán)請(qǐng)求回調(diào)
使用onRequestPermissionsResult(int ,String , int[])
方法處理回調(diào),上面說到了根據(jù)[requestPermissions()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))方法中的requestCode粗梭,我們就可以在回調(diào)方法中區(qū)分授權(quán)請(qǐng)求争便,看代碼:
@Override
publicvoidonRequestPermissionsResult(intrequestCode,Stringpermissions[],int[]grantResults){
switch(requestCode){
case MY_PERMISSIONS_REQUEST_READ_CONTACTS:{
// If request is cancelled, the result arrays are empty.
if(grantResults.length>0&&grantResults[0]==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;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
最后注意以下兩點(diǎn):
- 當(dāng)在Manifest文件中聲明了同一個(gè)permission group中的權(quán)限后,請(qǐng)求權(quán)限時(shí)不會(huì)列出具體的權(quán)限断医,只是將這個(gè)permission group權(quán)限進(jìn)行說明滞乙。對(duì)于permission group中的所有權(quán)限,用戶只需要授權(quán)一次鉴嗤,以后里面的所有其他權(quán)限都不需要在進(jìn)行授權(quán)(撤銷后等操作除外)斩启,系統(tǒng)會(huì)自動(dòng)認(rèn)為是授權(quán)并以PERMISSION_GRANTED為參數(shù)調(diào)用onRequestPermissionsResult方法,和彈出系統(tǒng)請(qǐng)求授權(quán)對(duì)話框點(diǎn)擊授權(quán)是一樣的效果醉锅。雖然這樣兔簇,對(duì)于每個(gè)授權(quán)還是需要進(jìn)行單獨(dú)請(qǐng)求授權(quán)操作。
-
Requesting Permissions at Run Time是當(dāng)target API >= 23并且系統(tǒng)為Android 6.0 (API level 23)及以上才有的,如不屬于這種情況APP還是像以前一樣在安裝的時(shí)候回提示所有需要的權(quán)限并授予這些權(quán)限垄琐。
看看簡單的demo效果;
compare.png
代碼:
https://gitlab.com/lujun/RuntimePermissionDemo
參考:
[http://developer.android.com/training/permissions/requesting.html#perm-request][1]
[][2]http://stackoverflow.com/questions/23527767/open-failed-eacces-permission-denied
[1]:http://developer.android.com/training/permissions/requesting.html#perm-request
[2]:http://stackoverflow.com/questions/23527767/open-failed-eacces-permission-denied