Android 新安全機制之動態許可權申請
Android6.0的SDK,對許可權的申請機制發生了一些變化。
在Android6.0之前,使用者安裝APP時就要為其授予對應的許可權,不然程式就無法安裝,而6.0之後,我們可以直接安裝,並可對應用的許可權進行管理,這樣極大的保護了使用者的隱私,但也給我們開發人員造成了些小麻煩(測試人員經常提單反映說某個功能未實現,其實是把許可權關閉造成的)
一、許可權分類
Google將應用的許可權分為兩類
- Normal Permissions:不需要使用者授予,可直接申請,如訪問網路、手機感測器等許可權,此類許可權一般不涉及使用者隱私。
- Dangerous Permission:需要使用者授予,需要動態申請,如撥打電話、訪問SD卡、獲取地理位置等許可權,此類許可權一般都涉及到了使用者隱私。
Normal Permissions列表
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_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
Dangerous Permission列表
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission .READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
可以發現,Dangerous Permission中,所有危險的 Android 系統許可權都是一個個許可權組,當我們在申請一組許可權中的其中一個許可權時,會對應申請整個許可權組,具體可以檢視Google的官方文件,描述的很詳細:
二、申請步驟
請求許可權的步驟,為了相容性,建議使用SDK 23版本或以上的v4包,這裡介紹的是v4包中的API
1、checkSelfPermission(Context context, String permission):
檢查使用者是否允許該許可權,有則返回0,無則返回-1,該方法在API 23版本才提供,為了相容性低版本,建議使用v4包中ContextCompat
類提供的checkSelfPermission()
方法
if (ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED) {
// 有許可權
}
if (ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_DENIED) {
// 無許可權
}
2、requestPermissions(Activity activity, String[] permissions, int requestCode):
申請許可權,會彈出一個系統對話方塊向用戶申請許可權,String[] permissions
傳入一個或一組許可權,int requestCode
為請求碼,用於在回撥監聽中標識許可權申請的結果。
ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.CAMERA}, STATE_CODE);
3、onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults):
監聽許可權請求的結果,int requestCode
對應requestPermissions()
方法中的requestCode
請求碼,int[] grantResults
表示對應許可權申請的結果,成功返回0,失敗返回-1
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case STATE_CODE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 申請成功
}
break;
}
}
4、shouldShowRequestPermissionRationale(Activity activity, String permission):
申請許可權的解釋,該方法只在大於等於6.0的版本起作用,在6.0前的版本只返回false,它的返回結果有以下幾種情況
- 當申請對應許可權時被使用者拒絕了一次,再次申請對應許可權時該方法就會返回true
- 如果使用者在第二次時選擇了“不再提醒”,則再次請求許可權時該方法只會返回false
- 使用者在手機許可權管理中設定為“禁止”,則該方法返回false
if (ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.CAMERA)) {
// 使用者再次拒絕時
}
三、常見問題:
1、onRequestPermissionsResult回撥失效:
不管許可權申請結果成功還是失敗,都沒有回撥onRequestPermissionsResult()方法
需要明白以下幾點,這個問題就好解決了:
如果你在一個Fragment頁面呼叫
requestPermissions()
方法,那麼當前Fragment頁面的onRequestPermissionsResult()
就會被回撥。如果你是通過
ActivityCompat.requestPermissions()
方法呼叫的,那麼Activity頁面的onRequestPermissionsResult()
就會被回撥。
所以,一般出現onRequestPermissionsResult()
沒有被回撥的情況,很可能是由於在Fragment頁面中使用ActivityCompat.requestPermissions()
方法請求許可權造成的,如果要在Fragment中申請許可權,應該使用當前Fragment頁面本身來申請。
2、checkSelfPermission返回始終為PackageManager.PERMISSION_GRANTED:
把應用執行在6.0系統時,不管使用者是否拒絕了這個許可權,checkSelfPermission返回的結果一直是PackageManager.PERMISSION_GRANTED。
如果碰到了這樣的問題,需要檢查你的App目標SDK是否是23以下,也就是targetSdkVersion < 23的時候,但APP卻執行在6.0以上的機器時,就會出現這樣的情況,對於targetSdkVersion < 23的應用,在6.0的機器執行時,我們需要使用PermissionChecker
類中的checkSelfPermission()
方法,才能正常獲取,所以我們在檢查使用者是否授予許可權時,需要進行兩套處理。
解決方法:
檢查應用的targetSdkVersion是否小於23的方法:
/**
* 獲取應用targetSdkVersion
* @param context 上下文
* @return 應用targetSdkVersion
*/
private static int getTargetSdkVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return info.applicationInfo.targetSdkVersion;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 0;
}
使用以下方法檢查是否有許可權:
/**
* 檢查是否有許可權
* @param context 上下文
* @param permission 申請的許可權
* @return true=有許可權 false=無許可權
*/
public static boolean checkHavePermissions(Context context, String permission) {
if (Build.VERSION.SDK_INT >= 23) {
if (getTargetSdkVersion(context) >= 23) {
return ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED;
} else {
return PermissionChecker.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED;
}
}
// 如果Android版本低於6.0,則預設有許可權
return true;
}
通過自定義的checkHavePermissions()
方法,即可解決checkSelfPermission()
返回失效的問題,使用者授予了許可權時返回true,未授予時返回false。