一行程式碼搞定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。有什麼問題歡迎討論交流。