關(guān)于Android6.0運(yùn)行時(shí)權(quán)限問題,大家應(yīng)該不會(huì)陌生,這個(gè)坑我早就傻傻的跳進(jìn)去過,說一個(gè)笑話,api23剛出來不久,在沒搞清新特性之前,我就在項(xiàng)目中用上了,發(fā)到線上的包因?yàn)橐粋€(gè)權(quán)限忘了判斷而崩潰,后來緊急熱修復(fù),還特意寫了一篇博客來總結(jié),言歸正傳,那么這個(gè)PermissionsDispatcher什么東西;
PermissionsDispatcher是一個(gè)用注解方式來處理Android6.0運(yùn)行時(shí)權(quán)限的庫(kù),旨在高效處理權(quán)限問題。

使用對(duì)比
首先看看PermissionsDispatcher怎么用,我們拿普通的權(quán)寫法來和它對(duì)比:
- 普通寫法
//發(fā)起一個(gè)權(quán)限檢測(cè)
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 我們需要向用戶解釋為什么需要這個(gè)授權(quán),主要是用戶已經(jīng)拒絕過一次了
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
} else {
//不需要解釋,
//請(qǐng)求權(quán)限,請(qǐng)求了權(quán)限一個(gè)數(shù)組
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS 是程序員自己定義的一個(gè)常量,在結(jié)果回調(diào)時(shí)判斷,類似于startActivityForResult()方法中的requestCode
}
}
//結(jié)果回調(diào)
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
//grantResults對(duì)應(yīng)于請(qǐng)求權(quán)限的那個(gè)String[]
switch (requestCode) {
//對(duì)應(yīng)剛才那個(gè)請(qǐng)求code
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// 如果權(quán)限取消,數(shù)組是空的
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//獲取權(quán)限成功
} else {
//獲取權(quán)限失敗
}
return;
}
//其他請(qǐng)求
}
}
- PermissionsDispatcher寫法
//類注解,標(biāo)注這個(gè)類需要權(quán)限
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
//需要檢查權(quán)限的方法
@NeedsPermission(Manifest.permission.CAMERA)
void showCamera() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
.addToBackStack("camera")
.commitAllowingStateLoss();
}
//對(duì)這個(gè)權(quán)限的解釋
@OnShowRationale(Manifest.permission.CAMERA)
void showRationaleForCamera(final PermissionRequest request) {
new AlertDialog.Builder(this)
.setMessage(R.string.permission_camera_rationale)
.setPositiveButton(R.string.button_allow, (dialog, button) -> request.proceed())
.setNegativeButton(R.string.button_deny, (dialog, button) -> request.cancel())
.show();
}
//權(quán)限被拒絕
@OnPermissionDenied(Manifest.permission.CAMERA)
void showDeniedForCamera() {
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show();
}
//用戶勾選了不再詢問且被拒
@OnNeverAskAgain(Manifest.permission.CAMERA)
void showNeverAskForCamera() {
Toast.makeText(this, R.string.permission_camera_neverask, Toast.LENGTH_SHORT).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
}
//真正調(diào)用showCamera方法:
MainActivityPermissionsDispatcher.showCameraWithCheck(this);
兩者寫法的對(duì)比可以看出,用PermissionsDispatcher寫的邏輯更加清晰,避免各種if判斷,顯得清爽;
如何引入
- 當(dāng)你項(xiàng)目中使用的Android Gradle Plugin版本大于2.2時(shí)
// module build.gradle
ependencies {
compile 'com.github.hotchemi:permissionsdispatcher:${latest.version}'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:${latest.version}'
}
- 當(dāng)Android Gradle Plugin版本小于2.2時(shí)
// project build.gradle
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
// module build.gradle
apply plugin: 'android-apt'
dependencies {
compile 'com.github.hotchemi:permissionsdispatcher:${latest.version}'
apt 'com.github.hotchemi:permissionsdispatcher-processor:${latest.version}'
}
至于為什么要區(qū)分gradle plugin版本2.2,可以查看android-apt這個(gè)庫(kù)的作者寫的聲明,和Android官方對(duì)Android Studio2.2新功能的介紹;你如果感興趣翻看,你會(huì)了解到jack編譯這個(gè)詞匯,這個(gè)有機(jī)會(huì)再深入了解吧;
源碼初探
1.down代碼
代碼在github上,我是先fork然后clone到本地,我選擇的是tag分支下2.2.0,并不是master;

小提示,這個(gè)工程依賴的包比較多,默認(rèn)都是從jcenter下,慢成狗,建議改為國(guó)內(nèi)鏡像,比如我的
// project build.gradle
allprojects {
repositories {
// jcenter()
maven {
url 'http://maven.aliyun.com/nexus/content/groups/public'
}
}
}
2.模塊概況
可以看出該項(xiàng)目由四個(gè)模塊構(gòu)成,分別是'library', 'processor', 'sample', 'lint'
include ':library', ':processor', ':sample', ':lint'
- sample是android app模塊,主要是使用的示例
- library是android lib模塊,主要定義了注解類和調(diào)用工具
- processor是java lib模塊,實(shí)現(xiàn)了編譯注解的邏輯
- lint是一個(gè)lint模塊,應(yīng)該是定義lint規(guī)則
3.library模塊
這個(gè)模塊主要就一堆注解的定義和一個(gè)操作權(quán)限的工具類:PermissionUtils.java
library中一共定義了五個(gè)注解:
- RuntimePermissions | 在一個(gè)Class上注冊(cè)權(quán)限
- NeedsPermission | 作用在需要檢測(cè)權(quán)限的方法上
- OnShowRationale | 作用在需要做權(quán)限解釋的方法上
- OnPermissionDenied | 作用在權(quán)限被拒的方法上
- OnNeverAskAgain | 作用在權(quán)限被拒且不再提示的方法上
再看PermissionUtils:
PermissionUtils.java是一個(gè)工具類,它的關(guān)鍵方法
//驗(yàn)證權(quán)限返回的結(jié)果是不是GRANTED
public static boolean verifyPermissions(int... grantResults) {
if (grantResults.length == 0) {
return false;
}
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//目標(biāo)權(quán)限在當(dāng)前版本是否存在
private static boolean permissionExists(String permission) {
// Check if the permission could potentially be missing on this device
Integer minVersion = MIN_SDK_PERMISSIONS.get(permission);
// If null was returned from the above call, there is no need for a device API level check for the permission;
// otherwise, we check if its minimum API level requirement is met
return minVersion == null || Build.VERSION.SDK_INT >= minVersion;
}
//是否已獲取該權(quán)限
public static boolean hasSelfPermissions(Context context, String... permissions) {
for (String permission : permissions) {
if (permissionExists(permission) && !hasSelfPermission(context, permission)) {
return false;
}
}
return true;
}
//是否需要作出解釋
public static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) {
for (String permission : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
return true;
}
}
return false;
}
//獲取當(dāng)前targetSdk版本
@TargetApi(Build.VERSION_CODES.DONUT)
public static int getTargetSdkVersion(Context context) {
if (targetSdkVersion != -1) {
return targetSdkVersion;
}
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion;
} catch (PackageManager.NameNotFoundException ignored) {
}
return targetSdkVersion;
}
4.processor模塊
processor是一個(gè)java模塊,是用來處理編譯時(shí)注解,如果對(duì)編譯時(shí)注解和android-apt不明白的,那么久需要補(bǔ)習(xí)一下了,具體可以參考這篇博客;

processor模塊是用kotlin寫的,現(xiàn)在就不講該模塊了,我準(zhǔn)備看一下kotlin語法,然后再單獨(dú)寫一篇來講解,期待哈;
其他
還有一個(gè)lint模塊和sample,sample不打算拿出來講,太simple了,lint我暫時(shí)講不動(dòng),等我補(bǔ)補(bǔ)相關(guān)知識(shí),到時(shí)候再做一個(gè)總結(jié)。
總結(jié)
PermissionsDispatcher這個(gè)庫(kù),很值得大家去使用,國(guó)內(nèi)的知乎在用,未來應(yīng)該會(huì)加普及吧;最后預(yù)告一下,我近期準(zhǔn)備把processor模塊用java實(shí)現(xiàn)一遍,期待!最后感謝作者hotchemi,感謝閱讀的朋友?。?!