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)用的情景。


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)完全交給用戶。

權(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如下
- ACCESS_NETWORK_STATE
- VIBRATE
- NFC
- BLUETOOTH
...
Dangerous Permissions如下
- group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS - 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)限管理界面,界面長得是這樣子的

既然是可以管理的,用戶就能取消權(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ù)選框。如下圖

當(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
框架依賴
- sample依賴permission-api,使用apt插件編譯permission-compiler;
- permission-api依賴permission-annotation
Thanks
- PermissionGen
- MPermissions
- 聊一聊Android 6.0的運(yùn)行時(shí)權(quán)限
- Java api的方式來生成代碼javapoet
- com.google.auto.service:auto-service:1.0-rc2,auto-service庫生成META-INF等信息