PermissionsHandle (Android 6.0 運(yùn)行時(shí)權(quán)限處理)

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

Android權(quán)限說明

6.0以前權(quán)限一刀切

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

安裝權(quán)限說明
安裝界面

6.0運(yùn)行時(shí)權(quán)限

從Android 6.0M 開始,系統(tǒng)引入了新的運(yùn)行時(shí)權(quán)限機(jī)制。以某個(gè)需要拍照的應(yīng)用為例,當(dāng)運(yùn)行時(shí)權(quán)限生效時(shí),其Camera權(quán)限不是在安裝后賦予,而是在應(yīng)用運(yùn)行的時(shí)候請(qǐng)求權(quán)限。比如當(dāng)用戶按下相機(jī)拍照按鈕后,看到的效果是這樣子的,接下來,對(duì)于Camera權(quán)限的處理權(quán)完全交給用戶。

請(qǐng)求拍照時(shí)

權(quán)限的分組

6.0系統(tǒng)對(duì)權(quán)限進(jìn)行了分組,一般包括如下幾類:

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

正常權(quán)限一般不涉及用戶隱私,是不需要用戶進(jìn)行授權(quán)的,比如訪問網(wǎng)絡(luò),手機(jī)震動(dòng)等;危險(xiǎn)權(quán)限一般是涉及用戶隱私的,需要用戶進(jìn)行授權(quán),比如讀取手機(jī)Sdcard,訪問通訊錄,打開相機(jī)等。

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進(jìn)行查看。
看到上面的dangerous permission會(huì)發(fā)現(xiàn)危險(xiǎn)權(quán)限都是一組一組的,分組對(duì)于我們會(huì)有什么影響嗎?的確是有影響的,如果app運(yùn)行在Android 6.x的系統(tǒng)上,對(duì)于授權(quán)機(jī)制是這樣子的,如果你申請(qǐng)某個(gè)危險(xiǎn)的權(quán)限,假設(shè)你的app早已被用戶授予了同一組的某個(gè)危險(xiǎn)權(quán)限,那么系統(tǒng)會(huì)立即授權(quán),而不需要彈窗提示用戶點(diǎn)擊授權(quán)。對(duì)于申請(qǐng)時(shí)彈出的dialog上面的文本說明也是對(duì)整個(gè)權(quán)限組的說明,而不是單個(gè)權(quán)限。(注意:權(quán)限dialog是不能進(jìn)行定制的)。
ps:不過需要注意的是,不要對(duì)權(quán)限組過多的依賴,盡可能對(duì)每個(gè)危險(xiǎn)權(quán)限都進(jìn)行正常的申請(qǐng),以為在后面的版本權(quán)限組則可能發(fā)生變化!

必須要支持運(yùn)行時(shí)權(quán)限么

目前應(yīng)用實(shí)際上是可以不需要支持運(yùn)行時(shí)權(quán)限的,但是最終肯定還是需要支持的,只是時(shí)間問題而已。

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

不支持運(yùn)行時(shí)權(quán)限會(huì)崩潰么

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

如果你的應(yīng)用將targetSdkVersion設(shè)置低于23,那么在6.x的系統(tǒng)上不會(huì)為這個(gè)應(yīng)用開啟運(yùn)行時(shí)權(quán)限機(jī)制,即按照以前的一刀切方式處理。
然而,6.x系統(tǒng)提供了一個(gè)應(yīng)用權(quán)限管理界面,界面長得是這樣子的

6.0應(yīng)用權(quán)限管理界面

既然是可以管理的,用戶就能取消權(quán)限,當(dāng)一個(gè)不支持運(yùn)行時(shí)權(quán)限的應(yīng)用某項(xiàng)權(quán)限被取消時(shí),系統(tǒng)會(huì)彈出一個(gè)對(duì)話框提醒撤銷的危害,如果用戶執(zhí)意撤銷,會(huì)帶來如下反應(yīng):

  • 如果你的應(yīng)用程序在運(yùn)行,則會(huì)被殺掉
  • 當(dāng)你的應(yīng)用再次運(yùn)行時(shí),可能出現(xiàn)崩潰

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

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

如果用戶撤銷了獲取DeviceId的權(quán)限,那么再次運(yùn)行時(shí),deviceId就是null,如果程序后續(xù)處理不當(dāng),就會(huì)出現(xiàn)崩潰。

相關(guān)API

6.0運(yùn)行時(shí)權(quán)限,我們最終都是要支持的,通常我們需要使用如下的API

  • int checkSelfPermission(String permission) 用來檢測(cè)應(yīng)用是否已經(jīng)具有權(quán)限
  • void requestPermissions(String[] permissions, int requestCode) 進(jìn)行請(qǐng)求單個(gè)或多個(gè)權(quán)限
  • void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用戶對(duì)請(qǐng)求作出響應(yīng)后的回調(diào)
  • shouldShowRequestPermissionRationale 判斷接下來的對(duì)話框是否包含”不再詢問“選擇框。

以請(qǐng)求Camera權(quán)限為例

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);
    }
}

當(dāng)用戶選擇允許,我們就可以在onRequestPermissionsResult方法中進(jìn)行響應(yīng)的處理,比如打開攝像頭,進(jìn)行下一步操作。當(dāng)用戶拒絕,你的應(yīng)用可能就開始危險(xiǎn)了,當(dāng)我們?cè)俅螄L試申請(qǐng)權(quán)限時(shí),彈出的對(duì)話框和之前有點(diǎn)不一樣了,主要表現(xiàn)為多了一個(gè)checkbox復(fù)選框。如下圖

再次請(qǐng)求拍照時(shí)

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

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

一個(gè)標(biāo)準(zhǔn)的申請(qǐng)流程

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");
}

批量申請(qǐng)

批量申請(qǐng)權(quán)限很簡單,只需要字符串?dāng)?shù)組放置多個(gè)權(quán)限即可。如請(qǐng)求代碼

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);
}

注意:間隔較短的多個(gè)權(quán)限申請(qǐng)建議設(shè)置成單次多個(gè)權(quán)限申請(qǐng)形式,避免彈出多個(gè)對(duì)話框,造成不太好的視覺效果。

注意事項(xiàng)

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

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

多系統(tǒng)問題

當(dāng)我們支持了6.0必須也要支持4.4,5.0這些系統(tǒng),所以需要在很多情況下,需要有兩套處理。比如Camera權(quán)限

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

PermissionHandle庫使用(封裝)

雖然權(quán)限處理并不復(fù)雜,但是需要編寫很多重復(fù)的代碼,PermissionHandle借鑒PermissionGen庫來封裝的,基于Annotation Processor編譯時(shí)注解的方式來實(shí)現(xiàn)運(yùn)行時(shí)權(quán)限申請(qǐng)回調(diào)。

引入

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'
}

使用

  • 申請(qǐng)權(quán)限
@OnClick(R.id.btn_camera)
    public void open(View view) {
        if (view.equals(mBtnOpenCamera)) {
            PermissionsHandle.requestPermissions(this, REQUEST_CODE_OPEN_CAMERA, Manifest.permission.CAMERA);
        }
    }
  • 根據(jù)授權(quán)情況進(jìn)行回調(diào)
    // 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);
    }

思路:反射實(shí)例化注解處理器生成的代理類,根據(jù)注解和requestCode找到方法,然后執(zhí)行即可。詳細(xì)請(qǐng)看PermissionsHandle

框架依賴

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

Thanks

Contacts

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容