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如下
- 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進行查看。
看到上面的dangerous permission會發現危險權限都是一組一組的,分組對于我們會有什么影響嗎?的確是有影響的,如果app運行在Android 6.x的系統上,對于授權機制是這樣子的,如果你申請某個危險的權限,假設你的app早已被用戶授予了同一組的某個危險權限,那么系統會立即授權,而不需要彈窗提示用戶點擊授權。對于申請時彈出的dialog上面的文本說明也是對整個權限組的說明,而不是單個權限。(注意:權限dialog是不能進行定制的)。
ps:不過需要注意的是,不要對權限組過多的依賴,盡可能對每個危險權限都進行正常的申請,以為在后面的版本權限組則可能發生變化!
必須要支持運行時權限么
目前應用實際上是可以不需要支持運行時權限的,但是最終肯定還是需要支持的,只是時間問題而已。
想要不支持運行時權限很簡單,只需要將targetSdkVersion設置低于23就可以了,意思是告訴系統,我還沒有完全在API 23(6.0)上完全搞定,不要給我啟動新的特性。
不支持運行時權限會崩潰么
可能會,但不是那種一上來就噼里啪啦崩潰不斷的那種。
如果你的應用將targetSdkVersion設置低于23,那么在6.x的系統上不會為這個應用開啟運行時權限機制,即按照以前的一刀切方式處理。
然而,6.x系統提供了一個應用權限管理界面,界面長得是這樣子的
既然是可以管理的,用戶就能取消權限,當一個不支持運行時權限的應用某項權限被取消時,系統會彈出一個對話框提醒撤銷的危害,如果用戶執意撤銷,會帶來如下反應:
- 如果你的應用程序在運行,則會被殺掉
- 當你的應用再次運行時,可能出現崩潰
為什么會可能崩潰的,比如下面這段代碼
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
框架依賴
- sample依賴permission-api,使用apt插件編譯permission-compiler;
- permission-api依賴permission-annotation
Thanks
- PermissionGen
- MPermissions
- 聊一聊Android 6.0的運行時權限
- Java api的方式來生成代碼javapoet
- com.google.auto.service:auto-service:1.0-rc2,auto-service庫生成META-INF等信息