1. 程式人生 > >一行程式碼搞定Android 6.0動態許可權申請

一行程式碼搞定Android 6.0動態許可權申請

1、前言

從Android 6.0(API 23)開始,對系統許可權做了很大的改變。在之前使用者安裝APP前,只是把APP需要使用的許可權列出來給使用者告知一下,APP安裝後都可以訪問這些許可權。從6.0開始,一些敏感許可權,需要在使用時動態申請,並且使用者可以選擇拒絕授權訪問這些許可權,已授予過的許可權,使用者也可以去APP設定頁面去關閉授權。這對使用者來說提高了安全性,可以防止一些應用惡意訪問使用者資料,但是對於開發來說,也增加了不少工作量,這塊不做適配處理的話,APP在訪問許可權的時候會容易crash。

2、許可權等級和許可權組

許可權主要分為normal、dangerous、signature和signatureOrSystem四個等級,常規情況下我們只需要瞭解前兩種,即正常許可權和危險許可權。

2.1、正常許可權

正常許可權涵蓋應用需要訪問其沙盒外部資料或資源,但對使用者隱私或其他應用操作風險很小的區域。應用宣告其需要正常許可權,系統會自動授予該許可權。例如設定時區,只要應用宣告過許可權,系統就直接授予應用此許可權。下面是截止到API 23的普通許可權(需翻牆訪問)

普通許可權 普通許可權
ACCESS_LOCATION_EXTRA_COMMANDS ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY ACCESS_WIFI_STATE
BLUETOOTH BLUETOOTH_ADMIN
BROADCAST_STICKY CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE CHANGE_WIFI_STATE
DISABLE_KEYGUARD EXPAND_STATUS_BAR
GET_PACKAGE_SIZE INSTALL_SHORTCUT
INTERNET KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS NFC
READ_SYNC_SETTINGS READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS REQUEST_INSTALL_PACKAGES
SET_ALARM SET_TIME_ZONE
SET_WALLPAPER SET_WALLPAPER_HINTS
TRANSMIT_IR UNINSTALL_SHORTCUT
USE_FINGERPRINT VIBRATE
WAKE_LOCK WRITE_SYNC_SETTINGS
2.2、危險許可權

危險許可權涵蓋應用需要涉及使用者隱私資訊的資料或資源,或者可能對使用者儲存的資料或其他應用的操作產生影響的區域。例如讀取使用者聯絡人,在6.0以上系統中,需要在執行時明確向用戶申請許可權。

2.3、許可權組

系統根據許可權用途又定義了許可權組,每個許可權都可屬於一個許可權組,每個許可權組可以包含多個許可權。例如聯絡人許可權組,包含讀取聯絡人、修改聯絡人和獲取賬戶三個許可權。
* 如果應用申請訪問一個危險許可權,而此應用目前沒有對應的許可權組內的任何許可權,系統會彈窗提示使用者要訪問的許可權組(注意不是許可權)。例如無論你申請READ_CONTACTS還是WRITE_CONTACTS,都是提示應用需要訪問聯絡人資訊。
* 如果使用者申請訪問一個危險許可權,而應用已經授權同許可權組的其他許可權,則系統會直接授權,不會再與使用者有互動。例如應用已經請求並授予了READ_CONTACTS許可權,那麼當應用申請WRITE_CONTACTS時,系統會立即授予該許可權。下面為危險許可權和許可權組:

許可權組 許可權
CALENDAR READ_CALENDAR
WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

3、執行時請求許可權

關於執行時請求許可權,官網介紹的很清楚,也有很多其他文章介紹,這裡只是簡單羅列一下。

3.1、檢查許可權

應用每次需要危險許可權時,都要判斷應用目前是否有該許可權。相容庫中已經做了封裝,只需要通過下面程式碼即可:

int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_CALENDAR);

如果有許可權則返回PackageManager.PERMISSION_GRANTED,否則返回PackageManager。PERMISSION_DENIED。

3.2、請求許可權

當應用需要某個許可權時,可以申請獲取許可權,這時會有彈出一個系統標準Dialog提示申請許可權,此Diolog不能定製,使用者同意或者拒絕後會通過方法onRequestPermissionsResult()返回結果。當用戶拒絕過此許可權申請時,再次申請Dialog上可以勾選不再提示,這種情況下,以後再申請許可權不會彈Dialog直接返回拒絕。所以一些依賴某些敏感許可權的應用,需要自己去處理,向用戶解釋 為什麼需要此許可權,說服使用者授予許可權。請求許可權程式碼如下:

ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE);
3.3、處理許可權請求響應

當用戶處理許可權請求後,系統會回撥申請許可權的Activity的onRequestPermissionsResult()方法,只需要覆蓋此方法,就能獲得返回結果

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


            } else {


                // permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
            return;
        }
    }
}
3.4、考慮使用intent

有很多許可權操作可以考慮呼叫其他應用,這樣的話當前應用就不需要申請許可權。例如想要獲取相機照相,可以通過ACTION_IMAGE_CAPTURE喚起相機應用去完成,相機應用會把照片返回。同樣撥打電話、訪問聯絡人,都可以考慮使用類似方法。相比較其他應用,這類專門的應用做一些操作更容易讓使用者接受。

4、 關於國產機6.0以下系統

部分國產廠商定製了系統(例如小米、華為),在6.0以下系統就可以單獨控制權限。但是通常它們處理的不夠徹底,程式碼中判斷是否授權的時候,返回的是已經授權。而真正去做操作的時候,卻會因為沒有許可權導致應用crash。例如我曾遇到過的場景:
* 訪問聯絡人,可以拿到Cursor物件,但是cursor.moveToFirst()會返回false。
* 訪問攝像頭時,獲取到的Camera物件為空

所以在低版本手機上,不要以為擁有系統授權就萬事大吉了,一定要多加條件判讀和測試。

5、PermissionGrantor,一行程式碼搞定動態許可權申請。

上面執行時請求許可權中,我們看到了許可權申請依賴於Activity和Fragment,和startActivityForResult()方法的使用類似,必須依賴於Activity和Fragment的回撥方法。正常情況下還能滿足需求,可是當申請的許可權的程式碼在一個獨立的模組中時,例如我封裝了一個UI控制元件,控制元件中某個操作需要申請許可權,或者專案採用了MVVM框架,需要在一些view類或者model類中申請許可權,這是處理起來就會很麻煩。
我在自己程式碼中就遇到類似情況,後面我採用了使用一個單獨的Activity來申請許可權,通過回撥的方式通知業務層授權結果。感覺使用起來挺方便,就把它放到maven倉庫中了,專案中通過加入如下依賴即可。

compile 'com.github.dfqin:grantor:2.1.0'
5.1 PermissionGrantor使用

申請許可權很簡單,只需要下面一句話即可。

PermissionsUtil.requestPermission(Activity activity, PermissionListener listener, 
    String[] permissions);

下面是一個申請攝像頭的例子:

private void requestCemera() {
        if (PermissionsUtil.hasPermission(this, Manifest.permission.CAMERA)) {
            //有訪問攝像頭的許可權
        } else {
            PermissionsUtil.requestPermission(this, new PermissionListener() {
                @Override
                public void permissionGranted(@NonNull String[] permissions) {
                   //使用者授予了訪問攝像頭的許可權
                }


                @Override
                public void permissionDenied(@NonNull String[] permissions) {
                    //使用者拒絕了訪問攝像頭的申請
                }
            }, new String[]{Manifest.permission.CAMERA});
        }
    }

PermissionsUtil.requestPermission還有兩個過載的實現。使用者拒絕授權時,會有一個預設Dialog提示使用者開通許可權。這個Dialog的內容可以定製,也可以不顯示此Dialog,詳情見Github。有什麼問題歡迎討論交流。

grant1.gif

grant2.gif

grant3.gif

6、參考