关于Android6.0运行时权限问题,大家应该不会陌生,这个坑我早就傻傻的跳进去过,说一个笑话,api23刚出来不久,在没搞清新特性之前,我就在项目中用上了,发到线上的包因为一个权限忘了判断而崩溃,后来紧急热修复,还特意写了一篇博客 来总结,言归正传,那么这个PermissionsDispatcher什么东西;
PermissionsDispatcher 是一个用注解方式来处理Android6.0运行时权限的库,旨在高效处理权限问题。
###使用对比
首先看看PermissionsDispatcher怎么用,我们拿普通的权写法来和它对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 //发起一个权限检测 if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // 我们需要向用户解释为什么需要这个授权,主要是用户已经拒绝过一次了 if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_CONTACTS)) { } else { //不需要解释, //请求权限,请求了权限一个数组 ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS); // MY_PERMISSIONS_REQUEST_READ_CONTACTS 是程序员自己定义的一个常量,在结果回调时判断,类似于startActivityForResult()方法中的requestCode } } //结果回调 @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { //grantResults对应于请求权限的那个String[] switch (requestCode) { //对应刚才那个请求code case MY_PERMISSIONS_REQUEST_READ_CONTACTS: { // 如果权限取消,数组是空的 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //获取权限成功 } else { //获取权限失败 } return; } //其他请求 } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 //类注解,标注这个类需要权限 @RuntimePermissions public class MainActivity extends AppCompatActivity { //需要检查权限的方法 @NeedsPermission(Manifest.permission.CAMERA) void showCamera() { getSupportFragmentManager().beginTransaction() .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance()) .addToBackStack("camera") .commitAllowingStateLoss(); } //对这个权限的解释 @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(); } //权限被拒绝 @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); } } //真正调用showCamera方法: MainActivityPermissionsDispatcher.showCameraWithCheck(this);
两者写法的对比可以看出,用PermissionsDispatcher写的逻辑更加清晰,避免各种if判断,显得清爽;
###如何引入
当你项目中使用的Android Gradle Plugin版本大于2.2时
1 2 3 4 5 // module build.gradle ependencies { compile 'com.github.hotchemi:permissionsdispatcher:${latest.version}' annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:${latest.version}' }
当Android Gradle Plugin版本小于2.2时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 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}' }
至于为什么要区分gradle plugin版本2.2,可以查看android-apt 这个库的作者写的声明 ,和Android官方 对Android Studio2.2新功能的介绍;你如果感兴趣翻看,你会了解到jack编译
这个词汇,这个有机会再深入了解吧;
源码初探 1.down代码 代码 在github上,我是先fork然后clone到本地,我选择的是tag分支下2.2.0,并不是master;
小提示 ,这个工程依赖的包比较多,默认都是从jcenter下,慢成狗,建议改为国内镜像,比如我的
1 2 3 4 5 6 7 8 9 // project build.gradle allprojects { repositories { // jcenter() maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } } }
2.模块概况 可以看出该项目由四个模块构成,分别是’library’, ‘processor’, ‘sample’, ‘lint’1 include ':library', ':processor', ':sample', ':lint'
sample是android app模块,主要是使用的示例
library是android lib模块,主要定义了注解类和调用工具
processor是java lib模块,实现了编译注解的逻辑
lint是一个lint模块,应该是定义lint规则
3.library模块 这个模块主要就一堆注解的定义和一个操作权限的工具类:PermissionUtils.java library中一共定义了五个注解:
RuntimePermissions | 在一个Class上注册权限
NeedsPermission | 作用在需要检测权限的方法上
OnShowRationale | 作用在需要做权限解释的方法上
OnPermissionDenied | 作用在权限被拒的方法上
OnNeverAskAgain | 作用在权限被拒且不再提示的方法上
再看PermissionUtils:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 PermissionUtils.java是一个工具类,它的关键方法 //验证权限返回的结果是不是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; } //目标权限在当前版本是否存在 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; } //是否已获取该权限 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; } //获取当前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是一个java模块,是用来处理编译时注解
,如果对编译时注解
和android-apt
不明白的,那么久需要补习一下了,具体可以参考这篇博客 ;
processor模块是用kotlin写的,现在就不讲该模块了,我准备看一下kotlin语法,然后再单独写一篇来讲解,期待哈;
其他 还有一个lint模块和sample,sample不打算拿出来讲,太simple了,lint我暂时讲不动,等我补补相关知识,到时候再做一个总结。
总结 PermissionsDispatcher这个库,很值得大家去使用,国内的知乎在用,未来应该会加普及吧;最后预告一下,我近期准备把processor模块用java实现一遍,期待!最后感谢作者hotchemi ,感谢阅读的朋友!!!