1. 程式人生 > >Android 新安全機制之動態許可權申請

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。