PermissionsHandle (Android 6.0 運行時權限處理)

An easy-to-use library for handling Android M runtime permissions based on the Annotation Processor.

Android權限說明

6.0以前權限一刀切

Android6.0以前的系統,所有權限都是一刀切處理方式,只要用戶安裝了應用,Manifest清單中申請的權限都會被賦予,且安裝后撤銷不了。當彈出安裝對話框后,用戶只有兩個選擇,要么選擇安裝,默認所有的敏感權限;要么拒絕安裝應用。所以,這種一刀切的處理方式,我們是沒有辦法只允許某些權限或者拒絕某些權限。例如,小米5手機安裝應用的情景。

安裝權限說明
安裝界面

6.0運行時權限

從Android 6.0M 開始,系統引入了新的運行時權限機制。以某個需要拍照的應用為例,當運行時權限生效時,其Camera權限不是在安裝后賦予,而是在應用運行的時候請求權限。比如當用戶按下相機拍照按鈕后,看到的效果是這樣子的,接下來,對于Camera權限的處理權完全交給用戶。

請求拍照時

權限的分組

6.0系統對權限進行了分組,一般包括如下幾類:

  • 正常權限(Normal Protection)
  • 危險權限(Dangerous)
  • 特殊權限(Particular)
  • 其他權限(一般很少用到)

正常權限一般不涉及用戶隱私,是不需要用戶進行授權的,比如訪問網絡,手機震動等;危險權限一般是涉及用戶隱私的,需要用戶進行授權,比如讀取手機Sdcard,訪問通訊錄,打開相機等。

Normal Permissions如下

  1. ACCESS_NETWORK_STATE
  2. VIBRATE
  3. NFC
  4. BLUETOOTH
    ...

Dangerous Permissions如下

  1. group:android.permission-group.CONTACTS
    permission:android.permission.WRITE_CONTACTS
    permission:android.permission.GET_ACCOUNTS
    permission:android.permission.READ_CONTACTS
  2. group:android.permission-group.CAMERA
    permission:android.permission.CAMERA
    ...

可以通過adb shell pm list permissions -d -g進行查看。
看到上面的dangerous permission會發現危險權限都是一組一組的,分組對于我們會有什么影響嗎?的確是有影響的,如果app運行在Android 6.x的系統上,對于授權機制是這樣子的,如果你申請某個危險的權限,假設你的app早已被用戶授予了同一組的某個危險權限,那么系統會立即授權,而不需要彈窗提示用戶點擊授權。對于申請時彈出的dialog上面的文本說明也是對整個權限組的說明,而不是單個權限。(注意:權限dialog是不能進行定制的)。
ps:不過需要注意的是,不要對權限組過多的依賴,盡可能對每個危險權限都進行正常的申請,以為在后面的版本權限組則可能發生變化!

必須要支持運行時權限么

目前應用實際上是可以不需要支持運行時權限的,但是最終肯定還是需要支持的,只是時間問題而已。

想要不支持運行時權限很簡單,只需要將targetSdkVersion設置低于23就可以了,意思是告訴系統,我還沒有完全在API 23(6.0)上完全搞定,不要給我啟動新的特性。

不支持運行時權限會崩潰么

可能會,但不是那種一上來就噼里啪啦崩潰不斷的那種。

如果你的應用將targetSdkVersion設置低于23,那么在6.x的系統上不會為這個應用開啟運行時權限機制,即按照以前的一刀切方式處理。
然而,6.x系統提供了一個應用權限管理界面,界面長得是這樣子的

6.0應用權限管理界面

既然是可以管理的,用戶就能取消權限,當一個不支持運行時權限的應用某項權限被取消時,系統會彈出一個對話框提醒撤銷的危害,如果用戶執意撤銷,會帶來如下反應:

  • 如果你的應用程序在運行,則會被殺掉
  • 當你的應用再次運行時,可能出現崩潰

為什么會可能崩潰的,比如下面這段代碼

TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String deviceId = telephonyManager.getDeviceId();
if (deviceId.equals(mLastDeviceId)) {
  //do something here
}

如果用戶撤銷了獲取DeviceId的權限,那么再次運行時,deviceId就是null,如果程序后續處理不當,就會出現崩潰。

相關API

6.0運行時權限,我們最終都是要支持的,通常我們需要使用如下的API

  • int checkSelfPermission(String permission) 用來檢測應用是否已經具有權限
  • void requestPermissions(String[] permissions, int requestCode) 進行請求單個或多個權限
  • void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用戶對請求作出響應后的回調
  • shouldShowRequestPermissionRationale 判斷接下來的對話框是否包含”不再詢問“選擇框。

以請求Camera權限為例

private static final int REQUEST_PERMISSION_CAMERA_CODE = 1;
@Override
public void onClick(View v) {
    if (!(checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)) {
        requestCameraPermission();
    }
}

private void requestCameraPermission() {
    requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION_CAMERA_CODE) {
        int grantResult = grantResults[0];
        boolean granted = grantResult == PackageManager.PERMISSION_GRANTED;
        Log.i(TAG, ">>> onRequestPermissionsResult camera granted = " + granted);
    }
}

當用戶選擇允許,我們就可以在onRequestPermissionsResult方法中進行響應的處理,比如打開攝像頭,進行下一步操作。當用戶拒絕,你的應用可能就開始危險了,當我們再次嘗試申請權限時,彈出的對話框和之前有點不一樣了,主要表現為多了一個checkbox復選框。如下圖

再次請求拍照時

當用戶勾選了”不再詢問“拒絕后,你的程序基本這個權限就Game Over了。
不過,你還有一絲希望,那就是再出現上述的對話框之前做一些說明信息,比如你使用這個權限的目的(一定要坦白)。

shouldShowRequestPermissionRationale這個API可以幫我們判斷接下來的對話框是否包含”不再詢問“選擇框。

一個標準的申請流程

if (!(checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED)) {
  if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
      Toast.makeText(this, "Please grant the permission this time", Toast.LENGTH_LONG).show();
    }
    requestReadContactsPermission();
} else {
  Log.i(TAG, "onClick granted");
}

批量申請

批量申請權限很簡單,只需要字符串數組放置多個權限即可。如請求代碼

private static final int REQUEST_CODE = 1;
private void requestMultiplePermissions() {
    String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
    requestPermissions(permissions, REQUEST_CODE);
}

注意:間隔較短的多個權限申請建議設置成單次多個權限申請形式,避免彈出多個對話框,造成不太好的視覺效果。

注意事項

由于checkSelfPermission和requestPermissions從API 23才加入,低于23版本,需要在運行時判斷 或者使用Support Library v4中提供的方法

  • ContextCompat.checkSelfPermission
  • ActivityCompat.requestPermissions
  • ActivityCompat.shouldShowRequestPermissionRationale

多系統問題

當我們支持了6.0必須也要支持4.4,5.0這些系統,所以需要在很多情況下,需要有兩套處理。比如Camera權限

if (Util.isOverMarshmallow()) {
    requestPermission();//6.x申請權限
} else {
    openCamera();//低于6.0直接使用Camera
}

PermissionHandle庫使用(封裝)

雖然權限處理并不復雜,但是需要編寫很多重復的代碼,PermissionHandle借鑒PermissionGen庫來封裝的,基于Annotation Processor編譯時注解的方式來實現運行時權限申請回調。

引入

project's build.gradle

buildscript {
    dependencies {
        // android-studio apt plugin 
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}

module's buid.gradle

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    apt 'com.nelson:permission-compiler:1.0.0'
    compile 'com.nelson:permission-api:1.0.0'
}

使用

  • 申請權限
@OnClick(R.id.btn_camera)
    public void open(View view) {
        if (view.equals(mBtnOpenCamera)) {
            PermissionsHandle.requestPermissions(this, REQUEST_CODE_OPEN_CAMERA, Manifest.permission.CAMERA);
        }
    }
  • 根據授權情況進行回調
    // open camera
    @PermissionGrant(REQUEST_CODE_OPEN_CAMERA)
    public void requestCameraSucess() {
        Toast.makeText(mContext, "Grant app access camera!", Toast.LENGTH_SHORT).show();
    }

    @PermissionDenied(REQUEST_CODE_OPEN_CAMERA)
    public void requestCameraFailed() {
        Toast.makeText(mContext, "Deny app access camera!!!", Toast.LENGTH_SHORT).show();
    }
    
    // request result
      @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        PermissionsHandle.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
    }

思路:反射實例化注解處理器生成的代理類,根據注解和requestCode找到方法,然后執行即可。詳細請看PermissionsHandle

框架依賴

  1. sample依賴permission-api,使用apt插件編譯permission-compiler;
  2. permission-api依賴permission-annotation

Thanks

Contacts

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容