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定义了一个带两个参数的构造方法,没有传参:

修改一下:

 

目前先学到这吧,关于剩下的逻辑放下篇了。

posted on 2020-07-20 14:31  cexo  阅读(932)  评论(0编辑  收藏  举报

导航