美文网首页AOP
Android APT 学习 Demo,一步步教你实现类似 Bu

Android APT 学习 Demo,一步步教你实现类似 Bu

作者: 牙膏很好吃 | 来源:发表于2019-11-15 17:33 被阅读0次

    说在前面:如果你尝试过别人的apt代码示例,但是不能自动生成代码,那多半是因为你的gradle版本太高,存在兼容性问题,有两种解决方案:
    ①降低你本地的gradle版本;
    ②参考下面的依赖文件,加上就可以解决,我发现网上大多数的demo代码中都没有这行代码,导致网上大多数示例的apt demo都没有生效

    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

    依赖.png
    1.首先附上git地址 https://github.com/dongdaniqng/Android-Apt-Demo,不想看文章的直接下载代码编译
    2.说明下demo结构 demo结构.png

    核心是下面的三个module:

    • apt-annotation:注解的声明类,声明你要使用的注解,是个java library;
    • apt-processor:注解处理类,作用就是获取到注解上面的view的id,然后根据id,生成findViewById的代码类,是个java library;
    • apt-sdk:反射类,主要用来执行绑定view的操作,是个android library;

    依赖关系:apt-annotation -> apt-processor->apt-sdk

    app使用依赖: app依赖.png
    3.apt-annotation详解

    这个module很简单,就是一个注解类,表明你想用这个注解作用域是保存在字节码阶段,作用在变量上面,除此之外没有其他任何作用。

    apt-annotation.png
    下面是apt-annotation模块的结构图,so easy,没难度
    apt-annotation代码结构.png
    4.apt-processor详解

    结构图先上为敬:


    apt-processor结构.png

    接下来讲解每个类的作用:

    • builu.gradle:很简单,需要依赖我们在apt-annotation声明的Bind注解,同时依赖注解框架和java代码生成框架
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.google.auto.service:auto-service:1.0-rc6'//注解框架
        implementation 'com.squareup:javapoet:1.11.1' //java代码生成
        annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'//注解框架
        implementation project(":apt-annotation")//声明需要使用的注解Module
    }
    
    • _$Processor:注解框架的核心类,主要有两个功能,1.找到它自己需要处理的注解,获取到它的值,也就是xml布局文件中的Id,2.根据id,生成含有java绑定代码的文件,over,over,没其他功能
    @AutoService(Processor.class)
    public class _$Processor extends AbstractProcessor {
    
        private Elements es;
        private ProcessingEnvironment pe;
        private Map<String, _$CreateFactory> factoryMap = new HashMap<>();
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            es = processingEnv.getElementUtils();
            pe = processingEnv;
        }
    
    
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> s = new HashSet<>();
            s.add(Bind.class.getCanonicalName());
            return s;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            factoryMap.clear();
            Set<? extends Element> temp  = roundEnv.getElementsAnnotatedWith(Bind.class);
            //存储控件的id
            for (Element e : temp) {
                VariableElement ve = (VariableElement) e;
                TypeElement te = (TypeElement) ve.getEnclosingElement();
                String fullName = te.getQualifiedName().toString();
                _$CreateFactory tempFactory = factoryMap.get(fullName);
                if (tempFactory == null) {
                    tempFactory = new _$CreateFactory(es,te);
                    factoryMap.put(fullName, tempFactory);
                }
                Bind bindAnnotation = ve.getAnnotation(Bind.class);
                int id = bindAnnotation.value();
                tempFactory.putElement(id,ve);
            }
            //根据注解类型,生成java文件,生成绑定代码
            for (String key : factoryMap.keySet()) {
                _$CreateFactory cf = factoryMap.get(key);
                JavaFile jf = JavaFile.builder(cf.getPackageName(),cf.generateClassCodeWithJavapoet())
                        .build();
                try {
                    jf.writeTo(pe.getFiler());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
    }
    

    上线说道这个注解处理器知道自己需要处理哪个类,就是根据下面方法的返回值来确定的,可以看到,我们返回了在apt-annotation中定义的Bind注解类。

    @Override
     public Set<String> getSupportedAnnotationTypes() {
            Set<String> s = new HashSet<>();
            s.add(Bind.class.getCanonicalName());
            return s;
    }
    

    接下来,就是我们注解处理器的核心代码,process方法,这个方法中,第一步,遍历获取所有的注解集合,然后存储相应的id:

            Set<? extends Element> temp  = roundEnv.getElementsAnnotatedWith(Bind.class);
            //存储控件的id
            for (Element e : temp) {
                VariableElement ve = (VariableElement) e;
                TypeElement te = (TypeElement) ve.getEnclosingElement();
                String fullName = te.getQualifiedName().toString();
                _$CreateFactory tempFactory = factoryMap.get(fullName);
                if (tempFactory == null) {
                    tempFactory = new _$CreateFactory(es,te);
                    factoryMap.put(fullName, tempFactory);
                }
                Bind bindAnnotation = ve.getAnnotation(Bind.class);
                int id = bindAnnotation.value();
                tempFactory.putElement(id,ve);
            }
    

    第二步,根据id,生成java文件,这其中使用到了javapoet,这是一个快速生成java代码的框架:

    //根据注解类型,生成java文件,生成绑定代码
            for (String key : factoryMap.keySet()) {
                _$CreateFactory cf = factoryMap.get(key);
                JavaFile jf = JavaFile.builder(cf.getPackageName(),cf.generateClassCodeWithJavapoet())
                        .build();
                try {
                    jf.writeTo(pe.getFiler());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
    • _$CreateFactory:生成以_AutoGenerate结尾的java代码类,使用javapoet,具体语法需要了解一下https://github.com/square/javapoet,不难,熟悉熟悉就会了。
    public class _$CreateFactory {
        private String className;
        private String packageName;
        private TypeElement te;
        private Map<Integer, VariableElement> variableElementMap = new HashMap<>();
    
        public _$CreateFactory(Elements es, TypeElement te) {
            this.te = te;
    
            PackageElement pe = es.getPackageOf(te);
            this.packageName = pe.getQualifiedName().toString();
            this.className = te.getSimpleName().toString() + "_AutoGenerate";
        }
    
        public void putElement(int id, VariableElement ve) {
            variableElementMap.put(id, ve);
        }
        
        //生成class
        public TypeSpec generateClassCodeWithJavapoet() {
            TypeSpec ts = TypeSpec.classBuilder(className)
                    .addMethod(generateMethodCodeWithJavapoet())
                    .addModifiers(Modifier.PUBLIC)
                    .build();
            return ts;
        }
    
        //生成方法
        private MethodSpec generateMethodCodeWithJavapoet(){
            ClassName cn = ClassName.bestGuess(te.getQualifiedName().toString());
            MethodSpec.Builder ms = MethodSpec.methodBuilder("bind")
                    .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
                    .addParameter(cn,"owner")
                    .returns(void.class);
            for (int id : variableElementMap.keySet()) {
                VariableElement ve = variableElementMap.get(id);
                String viewName = ve.getSimpleName().toString();
                String viewType = ve.asType().toString();
                ms.addCode("owner."+viewName+" = "+"("+viewType+")"
                        +"(((androidx.appcompat.app.AppCompatActivity)owner).findViewById("
                        +id+"));"
                );
            }
            return ms.build();
        }
    
        public String getPackageName(){
            return packageName;
        }
    }
    
    好了,事已至此,APT框架的事就已经完成了。 撒花

    这个时候,看一下我们的成果,你只需要在测试的app module build.gradle中,添加如下依赖,然后点击android studio 的 tools目录下make project,

     implementation project(":apt-annotation")
     kapt project(":apt-processor")
    

    下面是我们生成的以_AutoGenerate结尾的绑定类:

    auto-gen.png
    类里面内容:
    auto-gen-code.png
    接下来,如果你不想使用反射,你只需要在绑定的activity中执行如下红色标配的代码即可:
    code-bind.png

    但是 ,你有没有发现框架的缺陷,那就是需要每次生成代码以后,然后调用bind方法,很麻烦,所以作为一个有追求的程序员,怎么能容忍这种事情。

    wbf.jpg
    所以,有了下面的apt-sdk部分。
    5.apt-sdk

    这个模块结构太简单啦,上图为敬:

    apt-sdk结构.png
    具体代码:
    apt-sdk-code.png
    目的就是根据我们自动生成的类名去反射,然后调用绑定方法,然后我们只需要在acticity的基类里面,使用如下方法:
    bind-code-new.png
    看到那红色的大字了没有,对,就是他,加上就行了。

    但是,同时不要忘了在app 的build.gradle下添加上下面绿绿的代码:


    绿绿的代码.png
    6.至此,真的大功告成,但是,这个框架仍然存在很多缺陷,不支持fragment,绑定代码调用反射,性能不是最优等,有待完善啊
    lele.png

    相关文章

      网友评论

        本文标题:Android APT 学习 Demo,一步步教你实现类似 Bu

        本文链接:https://www.haomeiwen.com/subject/cglrictx.html