Android9.0动态运行时权限源码分析及封装改造<三>-----打造自己的权限申请框架上
流程回顾:
在上一次https://www.cnblogs.com/webor2006/p/13269742.html对于动态权限的整个执行流程进行了一个分析,接下来则开始撸码从0开始打造属于自己的权限申请框架,在正式撸码之前先来简单回顾一下整体权限申请的一个流程:
权限检测流程:
显示申请权限的流程:
权限申请流程:
编译时注解处理器:
用通常的方式来申请权限:
这里咱们先不用高大上的框架来申请权限,而是采用最最通用直白的方式,然后再慢慢基于它进行演变,这里以申请sdcard的权限为例,具体代码就不细说了,基本上都用过:
package com.permissionarchstudy.test; import android.Manifest; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; public class MainActivity extends AppCompatActivity { private static final int RESULT_CODE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 100; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { //TODO } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RESULT_CODE_PERMISSIONS_WRITE_EXTERNAL_STORAGE); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case RESULT_CODE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: StringBuilder builder = new StringBuilder(); for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { builder.append(permissions[i]); } } if (builder.length() > 0) { new AlertDialog.Builder(this).setTitle("权限授权提示") .setMessage("请授权一下权限,以继续功能的使用\n\n" + builder.toString()) .setPositiveButton("好的", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }).create().show(); } break; } } }
清单中增加一个sdcard的写入权限:
接着运行一下:
有木有发现,这种传统申请权限的代码写法还是比较麻烦的,反正如果是让我来自己申请我是比较畏难的,所以说接下来就得改善这种比较笨重的写法,要是能让权限申请成功与失败能回调到具体的方法中,像这样:
开始改造:
这里需要用到编译时注解技术,像之前https://www.cnblogs.com/webor2006/p/10582178.html学习手写ButterKnife时已经用过了,这里就不过多的来阐述其步骤,直接开干:
定义注解:
这里新建一个java library:
定义注解处理器:
接下来又来新建一个java library,用于进行注解的解析处理,也就是AnnotationProcessor,这块也已经用了好多次了,套路比较简单,如下:
然后添加annotation的依赖:
然后注解处理还得依赖于一个辅助库,这样对于处理器的注册就不用咱们手动弄了,如下:
编写注解生成逻辑:
新建一个处理类:
package com.permissionstudy.libcompiler; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.TypeElement; public class RuntimePermissionAbstractProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
然后将它注册一下,这里由于用了三方的这个依赖:
所以说此时注册非常之简单了,加个注解就完事了,如下:
指定要处理的注解类型:
这里覆写一个方法:
指定支持JDK的版本:
初始化两个辅助工具:
收集在我们Activity中所有标上注解的方法:
接下来写啥呢?这里得从最终应用的角度来思考了,目前咱们的注解其实是应用到我们的方法上的,所以先添加一下依赖:
这里有个小细节需要反问一下,为啥这里主app只依赖libcompiler,依然能使用libannotation的注解呢?api,提到它就秒懂了,传递依赖:
好,接下来则来到注解处理器中收集所有标上注解的方法,既然要收集,则肯定得定义一个集合存放喽,所以定义一个集合:
其中用到个实体,里面内容为空先占个位:
接下来则来开始进行注解的扫描收集:
接下来需要过滤一下方法是否是我们想要的类型,如下:
也有可能此方法是一个私有的或者是抽象未实现的,这种也不满足要求,所以这里新建一个工具类:
经过过滤之后,此时元素就是一个有效的方法,此时则需要将元素进行收集,既然是要存放到一个HashMap里,那key存啥呢?这里存方法的类名,那如何获取方法的类名呢?先看一下代码,这里会涉及到几种Element的类型:
上面的代码有点难以理解,这是因为得理解几种类型的Element,下面先来看一下:
有了key之后,接下来则可以将其缓存到HashMap当中了,如下:
但是很明显此时MethodInfo中木有存任何信息,所以接下来再来处理一下:
此时则需要在MethodInfo类中定义三个HashMap:
其中貌似方法的入参这块木有用到:
这里其实可以改造一下咱们的方法,如下:
也就是会将所有已授权或拒绝的权限也返回到我们的方法当中,此时咱们就可以用到这个入参了,如下:
private boolean handleAnnotationInfo(RoundEnvironment roundEnvironment, Class<? extends Annotation> annotaiton) { //根据注解来获得所有的元素,在这里也就是方法 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotaiton); for (Element element : elements) { if (!checkMethodValidate(element, annotaiton)) { return false; } ExecutableElement methodElement = (ExecutableElement) element; TypeElement enclosingElement = (TypeElement) methodElement.getEnclosingElement(); String className = enclosingElement.getQualifiedName().toString(); MethodInfo methodInfo = methodMap.get(className); if (methodInfo == null) { methodInfo = new MethodInfo(); methodMap.put(className, methodInfo); } Annotation annotationClass = methodElement.getAnnotation(annotaiton);//获得注解的类型 String methodName = methodElement.getSimpleName().toString();//获得方法的名称 List<? extends VariableElement> parameters = methodElement.getParameters();//获得方法的入参 if (annotationClass instanceof PermissionGranted) { if (parameters == null || parameters.size() < 1) { String message = "the method %s marked by annotation %s must have an unique parameter [String[] permissions]"; throw new IllegalArgumentException(String.format(message, methodName, annotationClass.getClass().getSimpleName())); } int requestCode = ((PermissionGranted) annotationClass).value(); methodInfo.grantedMethodMap.put(requestCode, methodName); } else if (annotationClass instanceof PermissionDenied) { if (parameters == null || parameters.size() < 1) { String message = "the method %s marked by annotation %s must have an unique parameter [String[] permissions]"; throw new IllegalArgumentException(String.format(message, methodName, annotationClass.getClass().getSimpleName())); } int requestCode = ((PermissionDenied) annotationClass).value(); methodInfo.deniedMethodMap.put(requestCode, methodName); } else if (annotationClass instanceof PermissionRational) { int requestCode = ((PermissionRational) annotationClass).value(); methodInfo.rationalMethodMap.put(requestCode, methodName); } } return true; }
编译时自动生成文件:
生成目标:
接下来则需要通过注解处理器来生成一个文件,该文件会将授权的结果给回调到咱们相应的方法上来,具体最终生成的样子长这样:
看到这个文件名是不是能联想到ButterKnife,基本思路雷同。
具体实现:
其代码的生成在之前也学习过,基本上就是用string的拼接来实现,下面开始。
接下来咱们需要获得要生成的文件名称,也就是:
怎么获取呢,如下:
其中获得类名的工具方法如下:
接下来需要实现一个接口:
这里将这个接口的声明放到另一个java library当中,然后这里统一可以import这里面的类,如下:
然后app添加它的依赖:
此时咱们就可以import这里面的所有类了,如下:
接下来则可以定义类了:
接下来则需要来生成里面的方法了:
这里则需要根据里面的三个集合来进行方法的生成,所以模板代码走起:
接下来咱们需要定义一下这个接口了:
其中为啥要定义一个泛型T呢?其实是指Activity,因为最终咱们要回调到Activity中的某个方法上来,所以需要有一个source,其中rational这个应用代码还木有在Activity中定义,下面定义一下:
此时对于Processor中的这块需要修改一下:
接下来则来实现具体方法的重载了,也比较简单,如下:
其它两个方法也类似,一口气贴出来了:
另外在RuntimePermissionAbstractProcessor.handleAnnotationInfo()中有处报错了:
这是因为我们给MethodInfo定义了一个带两个参数的构造方法,没有传参:
修改一下:
目前先学到这吧,关于剩下的逻辑放下篇了。