Android M Permission 運行時權限 學習筆記

從Android 6.0開始, 用戶需要在運行時請求權限, 本文對運行時權限的申請和處理進行介紹, 並討論了使用運行時權限時新老版本的一些處理.

Android應用權限簡要介紹

一個Android應用默認情況下是不擁有任何權限的, 這即是說, 在默認情況下, 一個應用是沒有權利去進行一些可能會造成不好影響的操作的. 這些不好的影響可能是對其它應用,操作系統,或者是用戶.
如果應用需要一些額外的能力,則它需要在AndroidManifest.xml中靜態地聲明相應的權限.
 
如果應用沒有在manifest中聲明權限, 卻使用了相應的功能, 在調用到相應功能的時候, 將會拋出異常.
比如程序要發送一個請求,卻忘記加Internet權限, 那麼在發送這個請求的時候程序就會拋出異常,一般不會catch這個異常,所以程序直接就崩潰了:
Caused by: java.lang.SecurityException: Permission denied (missing INTERNET permission?)
 
 
Android 6.0 (API 23) 發佈之前, 所有的權限都在安裝應用的時候顯示給用戶,用戶選擇安裝則表示全部接受這些權限, 之後無法撤銷對這些權限的授權.
Android 6.0開始, 一部分比較危險的權限需要在程序運行時顯式彈框,請求用戶授權.
至於什麼時候彈這個框,由應用程序自己決定.
對於其他權限,認爲不是很危險,所以仍然保持原來的做法,在用戶安裝應用程序時就予以授權.
還需要注意的是,在設置中,對於應用的危險權限,用戶可以選擇性地進行授權或者關閉.
 

Permission的保護等級

permission的保護等級通過protectionLevel屬性設置, 共有4種: normal,dangerous,signature,signatureOrSystem.
 
簽名相關的比較不常用, 剩下的兩種是normaldangerous.
Guides裏面對這兩種類型進行了討論: 官網Guides: https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
總結下來就是: 所有的權限仍然在manifest中靜態聲明, normal權限的在安裝的時候自動授權, 而dangerous的權限需要應用明確地請求用戶授權.
當然對於Android 6.0以下的手機,或者以前開發的舊應用來說, dangerous權限也是安裝時授權的, 具體看下一節的討論.
 
Dangerous Permissions: 
 
 
想要查看所有dangerous的權限, 也可以用命令:
adb shell pm list permissions -g -d
  

手機版本和程序版本的不同處理

這裏引用一段Guides裏面的原文:
  • If the device is running Android 6.0 (API level 23) or higher, and the app's targetSdkVersion is 23 or higher, the app requests permissions from the user at run-time. The user can revoke the permissions at any time, so the app needs to check whether it has the permissions every time it runs. For more information about requesting permissions in your app, see the Working with System Permissions training guide.
  • If the device is running Android 5.1 (API level 22) or lower, or the app's targetSdkVersion is 22 or lower, the system asks the user to grant the permissions when the user installs the app. If you add a new permission to an updated version of the app, the system asks the user to grant that permission when the user updates the app. Once the user installs the app, the only way they can revoke the permission is by uninstalling the app.
這裏頭要注意and和or的使用,說明了只有滿足targetSdkVersion和實際使用設備的版本都在23及以上的時候,纔會採用新的動態權限機制. 其他情況, 跟之前一樣, 在安裝和升級應用的時候就授權了所有的權限.
 
可以總結爲:
1.所有的權限都在manifest中聲明.
2.如果(1)你的app的targetSdkVersion是23及以上,並且(2)app運行在Android 6.0及以上的設備,危險權限必須動態請求.
當權限被拒絕,app理應還是能夠使用的,只不過權限相關的部分功能不能用.
3.上一條中的兩個條件(1)(2)沒有同時滿足,即屬於其他情況, 所有權限在安裝時請求,如果用戶不接受,則不安裝.
 
特別注意這種情況: 舊應用新系統.
如果targetSdkVersion小於23,即被認爲是Android 6.0發佈之前開發的應用, 還沒有兼容6.0.
這種應用即便是被裝在Android 6.0的機器上,也是採用原來的安裝即授予權限邏輯, 所有權限在應用安裝時全部被授權.
在Android 6.0的設備上安裝targetSdkVersion小於23的應用之後, 可以在應用的設置中查看,發現所有的dangerous權限狀態都是打開. 
所以不用擔心老的應用在Android 6.0上會各種亂崩.
 
但是用戶仍然可以在系統設置中禁用權限:
在模擬器上點擊授權開關的時候彈出了以下提示:
如果用戶執意要取消授權, 應用雖然不會直接崩潰,但是功能變爲默默無作爲狀態, 返回值可能變爲null或者0,進而引起無法預料的行爲或者崩潰. 
 

爲什麼要及時升級targetSdkVersion

這是因爲每一個版本的API有可能會產生新的權限,這些新增的權限, 對於targetSdkVersion比該API低的應用是自動獲取的.
所以targetSdkVersion最好是能及時寫到最新,這樣避免應用自動獲取到新API新增的權限.
 
結論: 對targetSdkVersion還不存在的權限是自動獲取到的.
 
其中”Automatic permission adjustments”那段.
 

Permission group

所有的權限都有自己的permission group.
系統彈框請求某一個permission時也是隻說明了它的類別,當用戶同意,系統會給予它該條permission.(只有這一條).
但是如果app已經有了該group下的另一條permission,系統將會自動授予權限(也即請求權限的callback直接返回),這過程中不與用戶交互.
 

動態權限請求的實現

因爲權限動態檢查相關的API是Android 6.0才加入的, 所以minSdkVersion不是23時,推薦使用SupportLibrary來實現,好處是: 程序裏不必加if來判斷當前設備的版本.

1.檢查權限狀態

如果執行的操作需要一個dangerous permission, 那麼每次在執行操作的地方都必須check你是否有這個permission, 因爲用戶可以在應用設置裏隨意地更改授權情況, 所以必須每次在使用前都檢查是否有權限.
 
檢查權限的方法: ContextCompat.checkSelfPermission()兩個參數分別是Context和權限名.
返回值是:PERMISSION_GRANTED if you have the permission, or PERMISSION_DENIED if not.
 
比如:
複製代碼
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS)) {
    //has permission, do operation directly
    ContactsUtils.readPhoneContacts(this);
    Log.i(DEBUG_TAG, "user has the permission already!");
} else {
    //do not have permission
複製代碼
 

2.動態請求權限

如果上面權限檢查的結果是DENIED, 那麼就需要顯式地向用戶請求這個權限了.
Android提供了幾個方法來動態請求權限, 調用這些方法會顯示出一個標準的Dialog, 這個Dialog目前是不能被定製的.
 
2.1有時候可能需要解釋爲什麼需要這個權限
有時候你可能會需要跟用戶解釋一下權限的用途.
注意不是每條權限都需要解釋,顯而易見的那種可以不解釋,太多的解釋會降低用戶體驗.
 
一種方式是,當用戶拒絕過這個權限,但是又用到了這個功能, 那麼很可能用戶不是很明白爲什麼app需要這個權限, 這時候就可以先向用戶解釋一下.
爲了發現這種用戶可能需要解釋的情形, Android提供了一個工具類方法: shouldShowRequestPermissionRationale()
如果app之前請求過該權限,被用戶拒絕, 這個方法就會返回true.
如果用戶之前拒絕權限的時候勾選了對話框中”Don’t ask again”的選項,那麼這個方法會返回false.
如果設備策略禁止應用擁有這條權限, 這個方法也返回false.
 
注意具體解釋原因的這個dialog需要自己實現, 系統沒有提供.
 
2.2請求權限
請求權限的方法是: requestPermissions() 傳入一個Activity, 一個permission名字的數組, 和一個整型的request code.
這個方法是異步的,它會立即返回, 當用戶和dialog交互完成之後,系統會調用回調方法,傳回用戶的選擇結果和對應的request code.
代碼:
複製代碼
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS)) {
    //has permission, do operation directly
    ContactsUtils.readPhoneContacts(this);
    Log.i(DEBUG_TAG, "user has the permission already!");
} else {
    //do not have permission
    Log.i(DEBUG_TAG, "user do not have this permission!");

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
            Manifest.permission.READ_CONTACTS)) {

        // Show an explanation 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.
        Log.i(DEBUG_TAG, "we should explain why we need this permission!");
    } else {

        // No explanation needed, we can request the permission.
        Log.i(DEBUG_TAG, "==request the permission==");

        ActivityCompat.requestPermissions(MainActivity.this,
                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.
    }
}
複製代碼
 
這個對話框是系統的,不能自定義.
經驗證, 請求權限對話框中的”Don’t ask again”的選項, 只有該條權限之前的狀態是Denied的時候,纔會出現.
以前從未授權(即第一次彈框), 或者之前的狀態是Granted(當然這種情況一般不會彈框詢問), 出現的彈框都是不帶該不再詢問的選項的.
 
2.3處理請求權限的響應
當用戶對請求權限的dialog做出響應之後,系統會調用onRequestPermissionsResult() 方法,傳回用戶的響應.
這個回調中request code即爲調用requestPermissions()時傳入的參數,是app自定義的一個整型值.
如果請求取消,返回的數組將會爲空.
 
代碼:
複製代碼
@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 > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // permission was granted, yay! Do the
                // contacts-related task you need to do.
                ContactsUtils.readPhoneContacts(this);
                Log.i(DEBUG_TAG, "user granted the permission!");

            } else {

                // permission denied, boo! Disable the
                // functionality that depends on this permission.
                Log.i(DEBUG_TAG, "user denied the permission!");
            }
            return;
        }

        // other 'case' lines to check for other
        // permissions this app might request
    }
}
複製代碼
 
 
系統自動回調的情況: 
有一些情形下,調用
1.自動授權: 如果用戶已經允許了permission group中的一條A權限,那麼當下次調用requestPermissions()方法請求同一個group中的B權限時, 系統會直接調用onRequestPermissionsResult() 回調方法, 並傳回PERMISSION_GRANTED的結果.
2.自動拒絕: 如果用戶選擇了不再詢問此條權限,那麼app再次調用requestPermissions()方法來請求同一條權限的時候,系統會直接調用onRequestPermissionsResult()回調,返回PERMISSION_DENIED.
 
 
 

Best Practices

Best Practices 總結:
1.用Intent啓動其他應用來完成功能.
2.只用真的需要的權限.
3.不要一次請求多個權限來煩用戶,有的權限可以等到要用的時候再請求.
4.向用戶解釋爲什麼需要這個權限.
5.從Android 6.0開始,每一條權限,都需要測試開關兩種狀態下是不是都能讓應用正常運行,而不是崩潰.
並且相關的權限可能會需要測試不同的組合.
 

ADB命令

可以用命令行來管理權限:
Use the adb tool to manage permssions from the command line:
  • List permissions and status by group:

    $ adb shell pm list permissions -d -g

  • Grant or revoke one or more permissions:
    $ adb shell pm [grant|revoke] <permission-name> ... 

參考資料:

 
 
 
第三方庫: 
 
發佈了4 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章