解讀Android官方開發指導 - 運行時權限

封面

系統權限簡介

Android出于系統穩定性以及用戶隱私方面的考慮,將應用程序訪問權限限制在各自的沙盒內。程序可以隨意訪問所在沙盒內部的資源或者信息,訪問沙盒外部的則必須明確的申請相關訪問權限。應用程序所需要的權限需要在AndroidManifest.xml文件中申明。如:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    
        package="com.douyoumi.permission">

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  

    <application ...>    
    ...  
    </application>

</manifest>

(本文出處:http://www.lxweimin.com/p/0beb6243d650)

系統權限根據敏感程度分為普通權限危險權限兩類。兩類權限都需要在AndroidManifest.xml文件中申明。在Android 5.1 (API level 22) 及其以下版本上,系統在APP安裝時要求用戶授權所有權限,否則APP不能安裝;而在Android 6.0及其以上版本上,系統在APP安裝時授權所有普通權限,危險權限需要在使用時動態讓用戶授權。這使得Android的權限管理更加靈活,用戶可以根據需要在設置應用中對應用的各個危險權限授予不同的權限。Android系統的權限管理不知道被多少人吐槽過,這一改進無疑是加分項。

危險權限

權限申請官方開發指導

Android 6.0上使用在AndroidManifest.xml中已經申明的危險權限時需要用戶授權。針對權限請求相關操作系統提供了三個API。下面結合Android官方的開發指導對這幾個API做下說明。

  1. checkSelfPermission() 檢查是否已經具有了相關權限。任何時候APP都要在執行需要危險權限的操作前去檢查是否具有相關權限,即使剛剛執行過這項操作,因為用戶很有可能去設置應用中關閉了相關權限。
  2. shouldShowRequestPermissionRationale() 判斷是否需要向用戶解釋,為什么需要這些權限。有時候用戶會不理解應用程序為什么需要這些權限。如,相機應用申請攝像頭使用權限用戶容易理解,但是相機應用申請地理位置使用權限可能會讓用戶產生疑惑,因為用戶很有能不知道相機需要保存每張照片的拍攝地點。這時候我們就需要做適當的解釋說明了。這個方法只有在APP請求過某一權限且用戶禁止APP使用該權限的時候返回true。在用戶授權了權限和禁止權限時勾選了“Don't ask again”選項的情況下都會返回false。Android官方開發指導還提到一點,為避免給用戶帶來糟糕的用戶體驗,這里的解釋說明應該是異步的,不要阻塞用戶的操作。時下很多適配了6.0的APP在這點上處理的都不盡如人意,有的根本沒有解釋說明,有的是彈出對話框,用戶體驗都不是很好。下文會給出了一個完美的解決方案。
  3. requestPermissions() 申請相關權限。調用這個方法后會彈出一個系統對話框來向用戶申請權限,APP不能自定義這個對話框的內容,這也就增加了上面提到的解釋說明的必要性。這里還有一點也需要交代一下。從上面危險權限列表中也可以看出,這些權限都是有分組的。如,READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE權限就是屬于STORAGE組的。分門別類不僅僅是為了方便容易閱讀,組內權限在申請上也是有關聯的。
  • 在申請組內某個權限時,彈出的系統對話框會顯示組名,而不是指明所申請的權限名。如,申請READ_EXTERNAL_STORAGE權限時,系統對話框提示請求“訪問sd卡”權限,但不會說明是請求的sd卡讀權限;
  • 申請權限時,如果組內有別的權限已經獲得了用戶授權,系統不再彈出詢問對話框,而是自動授權該權限。如,在申請WRITE_EXTERNAL_STORAGE權限時用戶已經授權了READ_EXTERNAL_STORAGE權限,系統則會自動授權WRITE_EXTERNAL_STORAGE權限,不再詢問用戶;
  • 即使有前一條規則存在,在使用每一條權限時都必須(不是應該)調用requestPermissions()方法來申請權限。如,在已經獲取了READ_EXTERNAL_STORAGE權限的情況下,使用WRITE_EXTERNAL_STORAGE權限時依然需要調用requestPermissions()方法來申請,否則就會因為權限問題導致寫sd卡失敗。

注意:危險權限在AndroidManifest.xml文件中也必須申明,否則動態申請會失敗。

下面代碼是Android官方開發指導中權限申請大致框架。它使用了Android Support Library中的方法。雖然Android 6.0以后的framework中都有這些方法,但是對于開發者來說使用Android Support Library中的方法更簡單,不用檢查sdk版本可以兼容低版本。

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {  
    // Should we show an explanation?  
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_EXTERNAL_STORAGE)) {    

        // Show an expanation to the user *asynchronously* -- don't blockthis 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_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);    
        // MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE is anapp-defined int constant.     
        // The callback method gets the result of the request.  
    }
}

requestPermissions()申請有結果后會回調onRequestPermissionsResult()方法,這種方式對于Android開發者一定不會陌生,因為與startActivityForResult()結果回調onActivityResult()方法類似。如下重載onRequestPermissionsResult()方法即可。

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {  
    switch (requestCode) {    
        case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {      
            // 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  
    }
}

由于我們使用了AndroidSupport Library庫記得在build.gradle文件中添加依賴。

dependencies {    
    ...
    compile 'com.android.support:appcompat-v7:23.2.1'
}

動態權限申請封裝類EasyPermissionsEx

以上就是Android官方開發指導關于動態權限申請的全部內容了。系統的API已經很簡潔了,但是用在項目中時依然會出現很多重復代碼,按慣例是要封裝一下。在github上搜索"Android permissions"會有很多開源庫。Google在github上也開源了一個關于動態權限的封裝庫easypermissions。看了幾個庫后,個人覺得easypermissions寫的最好,但是也有不足的地方。如,解釋為什么需要權限的地方這個庫也彈出一個對話框阻塞了用戶操作,不符合開發規范、還有用戶禁止權限時勾選了“Don't ask again”后依然會彈出對話框請求權限,用戶體驗不好。所以我在easypermissions庫的基礎封裝了一個EasyPermissionsEx庫。說是一個庫,其實只有一個300行左右的類,你可以直接拷貝到你的項目中使用。

前文也提到過在解釋為什么需要權限或者在用戶永久禁止權限后引導用戶去設置應用開啟權限時,使用對話框會給用戶帶來不好的體驗。EasyPermissionsEx采用的解決方案是使用snackbar來提醒用戶。整體效果如下。snackbar是一種輕量級的用戶交互,它不會阻塞用戶當前的操作,是從底部彈出一個bar來提示用戶,類似toast,但是snackbar又有一個button允許用戶操作。關于snackbar更多信息可以跳轉到官方文檔了解。

EasyPermissionsEx效果圖

Snackbar在Android Design Support Library庫中記得在build.gradle文件中添加依賴。

dependencies {    
    ...
    compile 'com.android.support:design:23.4.0'
}

EasyPermissionsEx權限請求邏輯如下。關于EasyPermissionsEx更多詳情可以去github查看wiki,也可以直接看代碼,因為它總共也就300行代碼。

EasyPermissionsEx權限申請流程圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容