基本环境搭建(auto&javapoet)

一、API采取背景,举例

主要为了解决客户端MVP架构中,V层和P层生命周期不同步时,生成空View保护性逻辑。

之前需要手写空View的代码,现在通过注解配置可自动生成,同理于黄油刀的@ BindView 注解。

通过配置该注解,在编译期 (compileDebugJavaWithJavac)会自动生成Java类。

强制实现抽象方法getEmptyView(),点击emptyView(接口实例化的对象)

Android 代码广场_Android 代码广场

Android 代码广场_java_02

如果Iview需要添加新的方法,它的子类需要不断重写新的方法,这样就很麻烦,就可以通过APT解决(编译器生成代码),类似于ButterKnife来做这个框架

Android 代码广场_java_03

这里会引用到当前的Activity,他把我们当前的实例保存下来

如何保存的呐,就是通过

Android 代码广场_java_04

参考butterknife源码

Android 代码广场_java_05

利用javapoet语法搭建编译环境

通过注解的方法,在编译器里来修饰相应的一个类(或接口)

新建一个Module,这次不选Android Library(有自己的库和gradle脚本,可以存放视频和图片,体积过大) ,而是选Java Library(就是编译期生成的源码)更轻量级就行了

Android 代码广场_Java_06

Android 代码广场_父类_07

模仿ViewInject

@Retention(RUNTIME)  //运行时 注解
@Target(TYPE) // 类 接口 注解
public @interface ViewInject {
    int mainlayoutid()  default  -1;
}

参考文档 Android连载课程

二、APT的使用

1    新建两个Java Lib

仓库依赖来源 https://github.com/google/auto

第一个annotation  专门存放编译期注解。

第二个 apt 专门存放生成这个Java代码的注解处理器,并在Gradle添加两个外部包和自定义注解依赖。

implementation 'com.squareup:javapoet:1.9.0'
implementation 'com.google.auto.service:auto-service:1.0-rc3'
implementation project(':annotation')

分别是配合apt便捷生成java文件的工具及特定路径下生成配置文件。

2   注解处理器

继承AbstractProcessor并复写process方法,同时添加下图三个注解。

如果apt这个Module是Android Library是引用不到AbstractProcessor这个抽象的注解处理器,只有Java Library可以

@AutoService(Processor.class) //生成;APT的入口
@SupportedAnnotationTypes({"com.web.god.annotation.MvpEmptyViewFactory"})
public class MvpProccesor extends AbstractProcessor {
    public Filer mFiler;
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        mFiler = processingEnv.getFiler();
        new MvpEmptyViewProcesor().process(roundEnv,this);
        return true;
    }
}

 

Android 代码广场_Java_08

@AutoService(Processor.class)注解,这个注解会自动在指定路径下生成一个配置文件:

Android 代码广场_Java_09

@SupportedAnnotationTypes注解,配置这个类所要处理的注解类型。(传入String类型参数,格式为:包名+类名);

Android 代码广场_Android 代码广场_10

@SupportedSourceVersion(SourceVersion.RELEASE_8)注解,JDK版本

3   集成APT

       项目Gradle 文件 依赖上述两个Java Lib。

        implementation project(':annotation')

        kapt/ annotationProcessor project(':apt')   (要看项目中是否有用 apply plugin: 'kotlin-kapt')

MvpEmptyViewProcessor  MVP空View Java代码生成器

这里的代码主要是用javapoet 这个库的语法通过该注解拿到的信息生成Java类。

参考 https://github.com/square/javapoet

Android 代码广场_父类_11

   

Android 代码广场_Android 代码广场_12

拿到生成相应代码逻辑

Android 代码广场_Java_13

大致逻辑:

  1. 获取被编译期注解修饰的类信息。
  2. 获取该类及父类结构里方法、参数、返回值信息。
  3. 根据这些信息生成该类的匿名对象。

 

写入文件时,传入的包名

Android 代码广场_java_14

通过常量获取类名

Android 代码广场_父类_15

 

/**
 *  把这个类里面的具体实现,生成逻辑用到javapoet
 */
public class MvpEmptyViewProcesor {

    String CLASS_NAME = "MvpEmptyViewFactory";

    /**
     *
     * @param roundEnv 编译环境
     * @param processor 继承来自AbstractProcessor;抽象编程
     */
    public void process(RoundEnvironment roundEnv, MvpProccesor processor) {
        try {
            //创建Class名字,类的修饰,增加类下面的方法(构造者设计模式)
            TypeSpec.Builder tb = TypeSpec.classBuilder(CLASS_NAME).addModifiers(PUBLIC, FINAL).addJavadoc("empty view by apt");
            //类下面的具体方法内容参数,内部逻辑;方法名=create;参数=mClass
            MethodSpec.Builder methodBuilder1 = MethodSpec.methodBuilder("create")
                    .returns(Object.class).addModifiers(PUBLIC, STATIC).addException(IllegalAccessException.class).addException(InstantiationException.class)
                    .addParameter(Class.class, "mClass");
            List<ClassName> mList = new ArrayList<>();
            //拼接字符串
            CodeBlock.Builder blockBuilder = CodeBlock.builder();
            //beginControlFlow()方法添加一个switch(),这里是“开始”
            blockBuilder.beginControlFlow(" switch (mClass.getCanonicalName())");
            //ElementFilter.typesIn()全局获取编译期被MvpEmptyViewFactory修饰所有类的信息
            for (TypeElement element : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(MvpEmptyViewFactory.class))) {
                ClassName currentType = ClassName.get(element);
                if (mList.contains(currentType)){
                    continue; //有类重复获取时,跳过
                }
                mList.add(currentType); //如果没有,就把当前现象记录到类List里面
                //生成本身的方法
                StringBuilder s = new StringBuilder();
                //类的信息去获取所有方法的集合
                List<? extends Element> enclosedElements = element.getEnclosedElements();
                for (int i = 0; i < enclosedElements.size(); i++) { //获取它本身所有方法的信息
                    if (enclosedElements.get(i) instanceof ExecutableElement) {
                        ExecutableElementBean elementBean = ExecutableElementParseUtil.parseElement((ExecutableElement) enclosedElements.get(i));
                        //通过拼接字符串信息去做
                        s.append("@Override ").append("public").append(" ").append(elementBean.returnType).append(" ").append(elementBean.methordName).append("(").append(elementBean.params).append(")").append(String.format("{%s}\n", ExecutableElementParseUtil.getReturnType(elementBean)));
                    }
                }
                //生成父类的接口方法(这是一个递归的操作),就是下面这个方法
                getSuperFun(element, s);
                //addStatement()添加一段话,通过占位符方式,case $S对应element.toString(),$T对应currentType,$L对应s(StringBuilder把所有字符串拼接到一起)
                blockBuilder.addStatement("case $S : \n return  new $T(){ \n$L }", element.toString(), currentType, s);
            }

            blockBuilder.addStatement("default: return null");
            //switch()“结束”
            blockBuilder.endControlFlow();
            //上面MethodSpec.methodBuilder("create"),create方法,它有个addCode的API,把这个switch()语法体加入进去
            methodBuilder1.addCode(blockBuilder.build());
            //上面TypeSpec.classBuilder(CLASS_NAME),就是MvpEmptyViewFactory类(@interface),addMethod()把create方法加进去
            tb.addMethod(methodBuilder1.build());
            JavaFile javaFile = JavaFile.builder("today.information.mvp", tb.build()).build();
            //这些类的信息通过writeTo()生成到对应包名路径today.information.mvp下面
            javaFile.writeTo(processor.mFiler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 递归获取 父类的方法
     *
     * @param element
     * @param s
     */
    private void getSuperFun(TypeElement element, StringBuilder s) {
        List<? extends TypeMirror> interfaces = element.getInterfaces();
        if (interfaces != null && interfaces.size() > 0) {
            for (int i = 0; i < interfaces.size(); i++) { //不断去遍历它的父类有哪些方法,包括它的父类的父类
                TypeMirror typeMirror = interfaces.get(i);
                if (typeMirror instanceof DeclaredType) {
                    Element element1 = ((DeclaredType) typeMirror).asElement();
                    List<? extends Element> innerElements = element1.getEnclosedElements();
                    for (int j = 0; j < innerElements.size(); j++) {
                        if (innerElements.get(i) instanceof ExecutableElement) {
                            ExecutableElementBean elementBean = ExecutableElementParseUtil.parseElement((ExecutableElement) innerElements.get(j));
                            s.append("@Override ").append("public").append(" ").append(elementBean.returnType).append(" ").append(elementBean.methordName).append("(").append(elementBean.params).append(")").append(String.format("{%s}\n", ExecutableElementParseUtil.getReturnType(elementBean)));
                        }
                    }
                    if (element1 instanceof TypeElement) {
                        getSuperFun((TypeElement) element1, s); //递归
                    }
                }
            }
        }
    }
}

要生成的效果

Android 代码广场_父类_16

注解生成器去遍历循环的作用

第一步:先获取被@MvpEmptyViewFactory注解修饰这个类的所有信息

第二步:然后拿到类的信息通过相应的方法去获取它本身自带的方法以及它父类的所有方法

Android 代码广场_Android 代码广场_17

建立MVP中空指针的保护机制

Android 代码广场_Java_18

 

Android 代码广场_Java_19

这样做是为了减少空指针异常,因为P层这个方法里面对View层做了一层弱引用,所以需要判空的

Android 代码广场_java_20

/**
 * 集成mvp 及 网络请求 快捷方式
 * 抽象类改成实现getEmptyView()泛型类型的方法
 */
//public abstract class BasePresenter<T extends IMvpView> extends BaseMvpPresenter<T>
public  class BasePresenter<T extends IMvpView> extends BaseMvpPresenter<T>{

    public BasePresenter(T view) {
        super(view);
    }

    public void submitTask(LfTask task) {
        TaskHelper.submitTask(task,task);
    }

    /**
     *  BasePresenter本身是泛型继承IMvpView,所以采取泛型
     * 通用逻辑来完成P层对APT生成MvpEmptyViewFactory类的使用
     * 这里使用到了P层对View层的弱引用,会出现空指针异常,需判空
     * @return 判空方式:try{}catch 未return 判空错误
     */
    @Override
    protected T getEmptyView() {
        T t = null;
        //GenericsUtils工具类获取当前类泛型里面的参数-Class
        Class superClassGenricType = GenericsUtils.getSuperClassGenricType(this.getClass(), 0);
        try {
            t = (T)MvpEmptyViewFactory.create(superClassGenricType); //强转为泛型T
        } catch (Exception e) {
            e.printStackTrace();
        }
        /**
         *  这样就可以使生成类MvpEmptyViewFactory生成IMainActivityContract类下@MvpEmptyViewFactory所注解的Iview接口下的所有方法
         *  同时Iview是继承IMvpView控制层的,可以减少空指针异常,因为P层这个方法里面对View层做了一层弱引用,有try{}catch来进行判空
         */
        return t;
    }
}

断点调试最重要

因为相对看这种新的API,很难看到,查到的资料都来自国外,中文的资料都比较少,所以最好的方法是通过断点调试了解大概,但是APT是通过编译器去执行一行行代码的,平时打的断点都打不到这里面来!!!解决方案如下:

4   生成及调试

在想要生成的地方用编译期注解进行修饰,然后Rebuild项目。

APT 调试   

三、经验总结


逻辑比较简单的场景,如逻辑较为复杂定位问题比较麻烦 其次 会影响编译速度,简单有:

findViewById,new一个对象...

生成的这个类是无法改的,直接打包到apk里面了,log日志也只能放到生成器里面打印

2.建议

      可以看看黄油刀ButterKnife的源码,对更深层次的理解APT有很大帮助。

查看接口有多少例子在继承它

Android 代码广场_Android 代码广场_21

Android 代码广场_Java_22

Android 代码广场_Java_23

注释掉

Android 代码广场_java_24

Android 代码广场_父类_25