zoukankan      html  css  js  c++  java
  • 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("请授权一下权限,以继续功能的使用
    
    " + 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中的这块需要修改一下:

    接下来则来实现具体方法的重载了,也比较简单,如下:

    其它两个方法也类似,一口气贴出来了:

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

  • 相关阅读:
    Spring Boot ELK Kafka 自定义断路器Circuit-Breaker
    据库主体在该数据库中拥有架构,无法删除解决方法
    AlloyImage
    引用嵌入式DLL方法
    C# C++ 字符串传递
    高效取图片像素的方法
    Microsoft Active Accessibility
    IIS配置支持APK下载
    C# gettime实现
    android快捷键
  • 原文地址:https://www.cnblogs.com/webor2006/p/13344912.html
Copyright © 2011-2022 走看看