跟离上一次https://www.cnblogs.com/webor2006/p/13344912.html的权限申请框架学习又过去一年多了,还差这篇的收尾,这次把它给完结了,不能半途而废。
编译时自动生成文件:
生成代码编写:
我们已经把所有标有注解的方法都收集起来了,接下来则可以根据咱们收集的这些信息来编写代码生成的逻辑,回到这块:
要想生成代码,需要使用到这个对象:
然后接下来则遍历咱们收集的方法集合来进行类的生成,如下:
其中MethodInfo中有几个私有方法需要将其公开化:
编译看效果:
annotationProcessor注册:
此时需要回到app module中,这样注册一下:
编译:
接下来编译看一下会不会报错,报错了,而且是一个经典的错:
com.android.tools.r8.a: Invoke-customs are only supported starting with Android O (--min-api 26)
解决起来也很简单,在gradle中配置一个JDK版本为8既可,如下:
再次编译,发现完全木有生成。。其实是这里的依赖有点问题,更改一下:
再次编译,类成功生成:
,不错还是报了个错:
呃,生成的类怎么首字母多了一个“$”呢? 其实是这块写得有问题:
这里这样修改就可以了:
然后还发现生成方法时的代码写得有点问题,顺便也给改了:
再编译,文件生成正常了:
很明显少了一个参数了,所以又报错了。。
解决也很简单,把接口定义改一下:
然后咱们在生成代码处也得对应的修改一下:
再编译,就正常啦:
abstractProcessor远程断点调试:
在继续往下进行代码编写之前,这里先暂停学习一个开发技巧,我们也知道在编写AnnotationProcessor如果出现问题是很难定位错误的对吧,其实它也是有调试技巧的,所以接下来看一下如何进行它的代码调试。
1、gradle.properties文件下增加:
org.gradle.daemon=true org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5105
2、项目的teminal中运行gradle --deamon开启守护进程:
(base) xiongweideMacBook-Pro:permissionstudy xiongwei$ ./gradlew --daemon Starting a Gradle Daemon, 4 incompatible Daemons could not be reused, use --status for details > Task :help Welcome to Gradle 6.1.1. To run a build, run gradlew <task> ... To see a list of available tasks, run gradlew tasks To see a list of command-line options, run gradlew --help To see more detail about a task, run gradlew help --task <task> For troubleshooting, visit https://help.gradle.org BUILD SUCCESSFUL in 10s 1 actionable task: 1 executed
我本机gradle的版本为:
3、配置调试程序:
点击ok之后,在运行这块需要切换至此:
4、点击调试启动按钮,然后在项目的terminal中运行gradle clean assembleDebug运行项目:
然后打几个断点:
然后再执行一下它:
此时看一下效果:
成功debug了,这样对于这块的问题排查就有一个非常好的方式了。
权限请求类库封装:
目前注解处理类的逻辑已经处理好了,接下来则需要封装咱们自己的权限请求类来调用它们了。
1、将libpermissionhelper改为module:
目前它是一个java library:
为啥要改呢?因为它里面需要用到Android的一个类,比如Activity,Frgment,如下:
2、新建文件:
package com.permissionstudy.libpermissionhelper; import android.app.Activity; import androidx.fragment.app.Fragment; public class PermissionHelper { public static void requestPermission(Activity activity, String[] permission, int requestCode) { doRequestPermission(activity, permission, requestCode); } public static void requestPermission(Fragment fragment, String[] permission, int requestCode) { doRequestPermission(fragment.getActivity(), permission, requestCode); } private static void doRequestPermission(Activity activity, String[] permission, int requestCode) { //TODO } }
3、6.0以下系统直接通过:
我们知道,Android的动态权限是在6.0以后才提出的,所以对于6.0及以下的默认都是全通过的,所以先来处理这个条件:
那它里面具体逻辑呢?其实就是应该找到注解处理器扫描自动生成的这个类:
代码也比较容易,直接贴出:
package com.permissionstudy.libpermissionhelper; import android.app.Activity; import android.os.Build; import androidx.fragment.app.Fragment; public class PermissionHelper { private static final String SUFFIX = "$$PermissionProxy"; public static void requestPermission(Activity activity, String[] permission, int requestCode) { doRequestPermission(activity, permission, requestCode); } public static void requestPermission(Fragment fragment, String[] permission, int requestCode) { doRequestPermission(fragment.getActivity(), permission, requestCode); } private static void doRequestPermission(Activity activity, String[] permission, int requestCode) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { //6.0及以下系统直接通过 doExecuteGrant(activity, requestCode, permission); return; } } //授权成功 private static void doExecuteGrant(Activity activity, int requestCode, String[] permission) { PermissionProxy proxy = findProxy(activity); proxy.granted(requestCode, activity, permission); } //找到Activity注解处理器生成的类 private static PermissionProxy findProxy(Activity activity) { Class<? extends Activity> aClass = activity.getClass(); try { Class<?> forName = Class.forName(aClass.getName() + SUFFIX); PermissionProxy proxy = (PermissionProxy) forName.newInstance(); return proxy; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } return null; } }
4、处理6.0以后系统权限:
不需要弹窗提示:
接下来则来处理6.0以后系统的权限逻辑了,当然是需要主动申请才行,而申请时可能想弹一个说明框,所以先做一个是否要弹框说明的逻辑:
需要弹窗提示:
首先对用户已经拒绝的权限进行一个弹窗,所以需要从用户申请权限中先要过滤出已经被用户拒绝的权限,如下:
那当有需要解释的拒绝权限,那怎么弹窗呢?其实是想调用到Activity的这个方法:
所以注解处理器生成的代理类就发挥作用了,如下:
也就是会调到这:
另外,此提示弹窗显示之后,当用户点击“好的”按钮时,应该再次发起被拒权限的申请,所以现在的问题Activity的PermissionHelper类怎么进行一个交互,很显然用接口回调的方式,新建一个接口:
然后PermissionProxy.rational()接口中就得增加一个这个回调参数了:
它一动,一系列的连锁反应也就来了,首先是Activity:
然后,在注解代码生成器中也得变一下:
然后再PermissionHelper的调用处就可以这样写了:
这里重新编译一下,确保代理类没问题:
真正发起权限申请:
逻辑也比较简单:
运行:
一切就绪,接下来咱们调用一下咱们的封装类,来测度一下权限申请的效果:
注意,记得对应的在Manifest.xml中也声明一下,不然你要申请的权限是不会真正进行申请的:
当权限被拒绝时,咱们应该也给用户一个提示,如下:
而当权限被用户拒绝之后,则需要给出一个已拒绝权限的列表:
另外,在这个回调中,也得把逻辑让咱们的PermissionHelper来处理了:
其处理逻辑为:
其中这里面增加了一个方法:
好,接下来咱们来运行看一下效果,发现没任何效果。。原因是有个笔误:
修改一下:
那,再运行,发现还是不对。。
我全新安装的APP,而且机型是大于6.0的系统,咋就直接提示申请成功了。。有问题,这时因为咱们的PermissionHelper有几个代码还是写得有问题,下面先来调整一下:
对于需要弹框的权限和真正发起权限申请的权限是需要区分开的:
好,下面再运行看一下:
嗯,木问题,弹出了我们测试时的三个权限的申请框了,接下来分几种场景来测一下,看有没有其它的bug:
1、允许一个权限,拒绝剩下的二个权限:
这里看出一个问题了,我明显是允许了写入sdcard的权限,拒绝了相机和手机状态读取的权限,为啥在权限授权提示中,貌似反了:
但是,如果我退出APP再进来,再提示就对了:
那接下来解决我们在授权时的结果提示的这个问题,经过debug,发现原因了:
其原因就是这个判断条件出问题了:
改为它:
此时重新来一次,app卸载安装一下:
嗯,修复了,不过还有一个细节,这里再调整一下,就是对于申请成功的权限,我们也把哪些申请成功的权限打出来,目前只提示了一个“申请成功”:
再运行:
2、再打开app,看拒绝的权限是否会再弹框提示?
那,此时我再退出app,重新打开app,那之前咱们拒绝的权限还会再次被弹窗提示出来么?试一下:
木问题。
3、把剩下的所有权限都允许:
接下来这一步又出问题了,点击“好的”,发现死循环了:
原因也是由于笔误,改一下:
应该是改成它:
好,再次运行看一下:
貌似完美了,最后再整体走一遍流程:
总结:
至此,整个权限申请框架就已经编写完成了,回忆一下编译时的流程:
而运行时的流程为:
最后把PermissionHelper类的整个源码贴出来供参考:
package com.permissionstudy.libpermissionhelper; import android.app.Activity; import android.content.pm.PackageManager; import android.os.Build; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import java.util.ArrayList; import java.util.List; public class PermissionHelper { private static final String SUFFIX = "$$PermissionProxy"; public static void requestPermission(Activity activity, String[] permission, int requestCode) { doRequestPermission(activity, permission, requestCode); } public static void requestPermission(Fragment fragment, String[] permission, int requestCode) { doRequestPermission(fragment.getActivity(), permission, requestCode); } private static void doRequestPermission(Activity activity, String[] permission, int requestCode) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { //6.0及以下系统直接通过 doExecuteGrant(activity, requestCode, permission); return; } boolean rational = shouldShowPermissionRational(activity, requestCode, permission); if (rational) { //如果需要弹窗,则下面的真正发起权限申请的逻辑则不应该执行 return; } doRealRequestPermission(activity, permission, requestCode); } //真正发起权限申请 private static void doRealRequestPermission(Activity activity, String[] permission, int requestCode) { List<String> deniedPermissions = findDeniedPermissions(activity, permission); if (deniedPermissions.size() > 0) { String[] denied = new String[deniedPermissions.size()]; deniedPermissions.toArray(denied); ActivityCompat.requestPermissions(activity, denied, requestCode); } else { //如果没有需要授权的权限,则就代表所有权限都申请成功了 doExecuteGrant(activity, requestCode, permission); } } private static boolean shouldShowPermissionRational(final Activity activity, final int requestCode, final String[] permission) { PermissionProxy proxy = findProxy(activity); //需要弹框说明:将用户已经拒绝的权限给出提示 List<String> shouldShowRationalePermissions = findShouldShowRationalePermissions(activity, permission); if (!shouldShowRationalePermissions.isEmpty()) { //调用到Activity中被@PermissionRational注解修饰的方法 String[] denied = new String[shouldShowRationalePermissions.size()]; shouldShowRationalePermissions.toArray(denied); return proxy.rational(requestCode, activity, denied, new PermissionRationalCallback() { @Override public void onRationalExecute() { //重新发起被用户拒绝了的权限申请 doRealRequestPermission(activity, permission, requestCode); } }); } return false; } //从用户申请的权限中过滤出需要弹窗提示的权限 private static List<String> findShouldShowRationalePermissions(Activity activity, String[] permissions) { List<String> rational = new ArrayList<>(); for (String permission : permissions) { if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { //系统判断一下看是否该权限需要解释,需要解释的才放到集合中 rational.add(permission); } } return rational; } //从用户申请的权限中过滤出已经拒绝了的 private static List<String> findDeniedPermissions(Activity activity, String[] permissions) { List<String> deniedPermissions = new ArrayList<>(); for (String permission : permissions) { if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) { //系统判断一下看是否该权限需要解释,需要解释的才放到集合中 deniedPermissions.add(permission); } } return deniedPermissions; } //授权成功 private static void doExecuteGrant(Activity activity, int requestCode, String[] permission) { PermissionProxy proxy = findProxy(activity); proxy.granted(requestCode, activity, permission); } //授权失败 private static void doExecuteDenied(Activity activity, int requestCode, String[] permission) { PermissionProxy proxy = findProxy(activity); proxy.denied(requestCode, activity, permission); } //找到Activity注解处理器生成的类 private static PermissionProxy findProxy(Activity activity) { Class<? extends Activity> aClass = activity.getClass(); try { Class<?> forName = Class.forName(aClass.getName() + SUFFIX); PermissionProxy proxy = (PermissionProxy) forName.newInstance(); return proxy; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } return null; } //权限结果回调 public static void onRequestPermissionsResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) { List<String> grantPermissions = new ArrayList<>(); List<String> deniedPermissions = new ArrayList<>(); for (int i = 0; i < grantResults.length; i++) { String permission = permissions[i]; if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { deniedPermissions.add(permission); } else { grantPermissions.add(permission); } } if (grantPermissions.size() > 0) { String[] grant = new String[grantPermissions.size()]; grantPermissions.toArray(grant); doExecuteGrant(activity, requestCode, grant); } if (deniedPermissions.size() > 0) { String[] denied = new String[deniedPermissions.size()]; deniedPermissions.toArray(denied); doExecuteDenied(activity, requestCode, denied); } } }