Android6.0中的運行時請求權限

原文鏈接:https://blog.lujun.co/2015/12/07/Android6.0%E4%B8%AD%E7%9A%84%E8%BF%90%E8%A1%8C%E6%97%B6%E8%AF%B7%E6%B1%82%E6%9D%83%E9%99%90/

問題來源于最近的一個項目中,原本好好的程序,在一臺Nexus 6(Android6.0)上測試發現所有需要保存圖片到本地的都不行,看報錯:


error_1.png

其實不僅僅是保存圖片這里報這個錯,還有打開相機的時候也會報錯。好了問題就這么開始了!

看到這里都知道是權限的問題所引起的原因,對,確實是因為權限的問題引起的,但是程序明明已經在Manifest中聲明了以上操作需要的權限,而且在以前測試的系統機型中都是沒問題的,為什么!突然間想到了Marshmallow發布的時候提及到的Requesting Permissions at Run Time這東西,對就是他的原因!

Requesting Permissions at Run Time究竟是啥,官方對他是如下介紹滴:
從Android6.0(API >= 23)開始,用戶在APP運行的時候授予其權限而不是像以前安裝的時候就通通授予了(以前授權方式好像沒什么卵用)。由于不在需要在安裝或更新APP的時候授予相關權限,這就簡化了APP的安裝過程。這也提高了用戶對APP功能的控制,比如:用戶可以選擇讓一個Camera APP使用Camera,用戶可以在任何時候在設置面板撤銷這個權限。。。
看完是不是有點像我們在國產ROM中常見到的每個應用運行時權限授予。

系統權限也被分成normaldangerous兩類:

  • Normal類的權限不會直接涉及到用戶隱私風險。如果APP在Manifest文件中聲明了Normal類的權限,系統會自動授予這些權限。
  • Dangerous類的權限可能會讓APP涉及到用戶機密的數據。如果APP在Manifest文件中聲明了Normal類的權限,系統會自動授予這些權限。如果在Manifest文件中添加了Dangerous類的權限,用戶必須明確的授予對應的權限后APP才具有這些權限。
    關于哪些權限屬于Normal類,哪些屬于Dangerous類,如下圖:


    normal-dangerous-permissions.png

更詳細請看normal-dangerous-permissions文檔。

現在我們知道了在APP中normal和dangerous類的權限都需要Manifest文件中聲明,但是在不同的版本系統和target SDK效果是不一樣的,有如下幾點需要注意:

  • 系統Android 5.1以下或target SDK 22以下,只要在Manifest文件中聲明了需要的權限,用戶在安裝APP的時候就會授予相應的權限;如果不授予當然APP也無法安裝。
  • 如果設備運行在6.0以上并且你的應用的target SDK版本>=23,APP除了需要在Manifest文件中聲明相應的權限之外,還要在APP運行時向用戶進行請求每個dangerous類的權限。用戶可以選擇授予或不授予該權限,即使用戶不授予該權限APP也可以繼續運行,但是相關的需要權限的操作是沒法進行的。

使用系統提供的API檢查并請求權限

a. 檢查權限

以為用戶可以隨時撤銷對APP的授權,所以在每次準備進行需要dangerous類權限操作的時候,需要檢查APP是否具有對應的權限。使用[ContextCompat.checkSelfPermission()](http://developer.android.com/reference/android/support/v4/content/ContextCompat.html#checkSelfPermission(android.content.Context, java.lang.String))檢查權限,代碼如下:

// 檢查activity是否有寫日程的權限
// Assume thisActivity is the current activity
intpermissionCheck=ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.WRITE_CALENDAR);

上面代碼,如果APP有該權限返回PackageManager.PERMISSION_GRANTED,APP接著可以進行對應操作;如果沒有權限,以上方法返回PERMISSION_DENIED,APP需要明確的向用戶請求授權。

b. 請求權限

如何使用系統的API向用戶請求dangerous類的權限,也很簡單,support-library都基本上幫我們做好了。首先這個權限已經在Manifest文件中聲明(廢話么),然后向用戶請求該權限,同意了你就用,否則想都別想(用戶是上帝)。Google是這么解釋why the app needs permissions的:

在某些情況下,你可能想讓用戶理解為啥你的APP需要這個權限。比如用戶打開了一個拍攝APP(好吧,又是Camera),用戶不會對這個APP需要使用Camera感到奇怪,但是用戶可能不能理解為什么這破APP還需要使用我的位置和聯系人數據(不能忍了)!所以在你請求某個權限之前,你應該考慮提供一段解釋性的話給用戶。請記住你不是要靠這點解釋性的話就讓用戶徹底的明白你為什么需要這個權限,如果你解釋過多,用戶覺得沒卵用直接卸了APP。只有當用戶拒絕了你權限請求之后才是使用那段解釋性的話最好的時候,因為如果用戶一直嘗試使用某個需要權限的功能,但是卻又一直不授予該權限,這就表明用戶真不知道為什么這個功能需要這個這個權限,在這種情況下,你的解釋性的話就派上用場了。

Android提供[shouldShowRequestPermissionRationale()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String))方法求向用戶展示為啥你需要這個權限,當用戶之前已經請求過該權限并且拒絕了授權這個方法返回true。
注意:如果用戶拒絕權限請求的時候選擇了Don’t ask again選項,上面的方法返回false,當然如果設備本身就不允許有這個權限也是返回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))就是請求權限方法,異步方法。看看這個方法需要三個參數:

@param activity The target activity
@param permissions The requested permissions(這里就是你需要申請的權限,在Manifest類中可以找到你需要的權限)
@param requestCode Application specific request code to match with a result  reported to onRequestPermissionsResult(int, String[], int[])}(標志這次授權的唯一請求碼,當用戶進行授權操作后在回調方法中可以根據這個標識符進行區分不同的授權操作)

有個缺點就是使用系統請求授權API你不能自定義樣式,請求授權彈出來的是標準的Android Dialog(如下圖,遵循Android標準蠻好的),如果你希望提供一些信息之類的應該在[requestPermissions()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))之前進行操作。

request_permission_dialog.png

c. 處理授權請求回調

使用onRequestPermissionsResult(int ,String , int[])方法處理回調,上面說到了根據[requestPermissions()](http://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))方法中的requestCode,我們就可以在回調方法中區分授權請求,看代碼:

@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
      }
}

最后注意以下兩點:

  • 當在Manifest文件中聲明了同一個permission group中的權限后,請求權限時不會列出具體的權限,只是將這個permission group權限進行說明。對于permission group中的所有權限,用戶只需要授權一次,以后里面的所有其他權限都不需要在進行授權(撤銷后等操作除外),系統會自動認為是授權并以PERMISSION_GRANTED為參數調用onRequestPermissionsResult方法,和彈出系統請求授權對話框點擊授權是一樣的效果。雖然這樣,對于每個授權還是需要進行單獨請求授權操作。
  • Requesting Permissions at Run Time是當target API >= 23并且系統為Android 6.0 (API level 23)及以上才有的,如不屬于這種情況APP還是像以前一樣在安裝的時候回提示所有需要的權限并授予這些權限。
    看看簡單的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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容