Android架构师-组件化-组件化APT介绍与使用 4

一:什么是APT

1:定义

Android架构师-组件化-组件化APT介绍与使用 4_第1张图片

对于注解,可以查看前面的文章 

2:结构体语言(APT是结构体语言)

Android架构师-组件化-组件化APT介绍与使用 4_第2张图片

3:Element程序元素

Android架构师-组件化-组件化APT介绍与使用 4_第3张图片

4:需要掌握的API

Android架构师-组件化-组件化APT介绍与使用 4_第4张图片

5:开发环境的兼容

Android架构师-组件化-组件化APT介绍与使用 4_第5张图片

二:开始撸码

接上文中,我们用注解处理器(APT)来生成类,包括Retrofit,EventBus里都用到了。

1:新建一个java Library:annotation

2:在此Library中新建Arouter类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Activity使用的布局文件注解
 * 
    *
  • @Target(ElementType.TYPE) // 接口、类、枚举、注解
  • *
  • @Target(ElementType.FIELD) // 属性、枚举的常量
  • *
  • @Target(ElementType.METHOD) // 方法
  • *
  • @Target(ElementType.PARAMETER) // 方法参数
  • *
  • @Target(ElementType.CONSTRUCTOR) // 构造函数
  • *
  • @Target(ElementType.LOCAL_VARIABLE)// 局部变量
  • *
  • @Target(ElementType.ANNOTATION_TYPE)// 该注解使用在另一个注解上
  • *
  • @Target(ElementType.PACKAGE) // 包
  • *
  • @Retention(RetentionPolicy.RUNTIME)
    注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容
  • *
* * 生命周期:SOURCE < CLASS < RUNTIME * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解 * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃 * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解 */ @Target(ElementType.TYPE) // 该注解作用在类之上 @Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作。注解会在class文件中存在 public @interface ARouter { // 详细路由路径(必填),如:"/app/MainActivity" String path(); // 路由组名(选填,如果开发者不填写,可以从path中截取出来) String group() default ""; }

然后我们在app中添加依赖:

 // 依赖注解
    implementation project(':annotation')

然后我们尝试对ManActivity添加此自定义注解

@ARouter(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {

注解写好了 我们就需要一个注解处理器(APT),新建Java Library:compiler

在写之前 强烈推荐使用阿里的公共镜像。添加到项目的build.gradle中:

buildscript {
    repositories {
        // 超级实用:某某影响,很多被墙,强烈推荐阿里云镜像更新
        maven {
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'
        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        // 超级实用:某某影响,很多被墙,强烈推荐阿里云镜像更新
        maven {
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
        google()
        jcenter()
        
    }
}

然后开始在compiler的build.gradle中添加注解处理器,并且

    // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
    //这个是谷歌为我们提供的处理注解的一种服务,就是为了注册注解,并为他生成一个文件
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

    // 引入annotation,让注解处理器-处理注解
    implementation project(':annotation')

新建一个对ARouter注解处理的处理器:ARouterProcessor

注意导包

Android架构师-组件化-组件化APT介绍与使用 4_第6张图片

import com.google.auto.service.AutoService;
import com.netease.annotation.ARouter;

import java.io.IOException;
import java.io.Writer;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * 编码此类1句话:细心再细心,出了问题debug真的不好调试
 */
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理(新增annotation module)
@SupportedAnnotationTypes({"com.netease.annotation.ARouter"})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions("content")
public class ARouterProcessor extends AbstractProcessor {

    // 操作Element工具类 (类、函数、属性都是Element)
    private Elements elementUtils;

    // type(类信息)工具类,包含用于操作TypeMirror的工具方法
    private Types typeUtils;

    // Messager用来报告错误,警告和其他提示信息
    private Messager messager;

    // 文件生成器 类/资源,Filter用来创建新的源文件,class文件以及辅助文件
    private Filer filer;

    // 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        // 父类受保护属性,可以直接拿来使用。
        // 其实就是init方法的参数ProcessingEnvironment
        // processingEnv.getMessager(); //参考源码64行
        elementUtils = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        // 通过ProcessingEnvironment去获取build.gradle传过来的参数
        String content = processingEnvironment.getOptions().get("content");
        // 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e那么好使
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }

    /**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param set              使用了支持处理注解的节点集合(类 上面写了注解)
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解。
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) return false;

        // 获取所有带ARouter注解的 类节点
        Set elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        // 遍历所有类节点
        for (Element element : elements) {
            // 通过类节点获取包节点(全路径:com.netease.xxx)
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            // 获取简单类名
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
            // 最终想生成的类文件名
            String finalClassName = className + "$$ARouter";

            // 公开课写法,也是EventBus写法(https://github.com/greenrobot/EventBus)
            try {
                // 创建一个新的源文件(Class),并返回一个对象以允许写入它
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
                // 定义Writer对象,开启写入
                Writer writer = sourceFile.openWriter();
                // 设置包名
                writer.write("package " + packageName + ";\n");

                writer.write("public class " + finalClassName + " {\n");

                writer.write("public static Class findTargetClass(String path) {\n");

                // 获取类之上@ARouter注解的path值
                ARouter aRouter = element.getAnnotation(ARouter.class);

                writer.write("if (path.equals(\"" + aRouter.path() + "\")) {\n");

                writer.write("return " + className + ".class;\n}\n");

                writer.write("return null;\n");

                writer.write("}\n}");

                // 最后结束别忘了
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return true;
    }
}

那么我们传参应该怎么办呢?我们需要在app的build.gradle的defaultConfig中添加下面的代码:

    // 在gradle文件中配置选项参数值(用于APT传参接收)
        // 切记:必须写在defaultConfig节点下
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [content : 'hello apt']
            }
        }

类上的注解注明由哪个注解处理器接收此参数

// 注解处理器接收的参数
@SupportedOptions("content")

 init方法除了初始化一些工具类,还可以接收参数


    // 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        // 父类受保护属性,可以直接拿来使用。
        // 其实就是init方法的参数ProcessingEnvironment
        // processingEnv.getMessager(); //参考源码64行
        elementUtils = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        // 通过ProcessingEnvironment去获取build.gradle传过来的参数
        String content = processingEnvironment.getOptions().get("content");
        // 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e那么好使
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }

真正处理的方法是process方法,我们的注解处理器需要去扫描,去哪扫描呢?我们需要在app

中添加此注解处理器的依赖:

   // 依赖注解处理器
    annotationProcessor project(':compiler')

好的 此时我们来运行一下,看看在init方法中的打印:

Android架构师-组件化-组件化APT介绍与使用 4_第7张图片

回到process方法,这个方法的主要作用就是扫描到使用ARouter自定义注解的类,将这些类都扫描到一个集合中,

然后生成一个JavaFileObject

我们可以看下EventBus的源码 也是这么处理的:

Android架构师-组件化-组件化APT介绍与使用 4_第8张图片

源码地址https://github.com/greenrobot/EventBus/blob/master/EventBusAnnotationProcessor/src/org/greenrobot/eventbus/annotationprocessor/EventBusAnnotationProcessor.java

通过Filter(文件生成器),就是由一行一行的字符串把她拼接成一个java类。

那写法的规则是什么呢?我们要生成一个什么样的类呢?我们来新建一个模拟类

/**
 * 模拟APT生成后的文件样子
 */
public class XActivity$$ARouter {

 //我们是通过一个详细的路径,来找到Class,最终完成跳转,其实最后还是通过类加载完成的。
//我们需要这个,我们不像一步步写的东西,让APT帮我们来完成,最后放到Build中,最终打包到apk中来
    public static Class findTargetClass(String path) {
        if (path.equals("/app/MainActivity")) {
            return MainActivity.class;
        }
        return null;
    }
}

最后生成的过程如下

 /**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param set              使用了支持处理注解的节点集合(类 上面写了注解)
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解。
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) return false;

        // 获取所有带ARouter注解的 类节点
        Set elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        // 遍历所有类节点
        for (Element element : elements) {
            // 通过类节点获取包节点(全路径:com.netease.xxx)
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            // 获取简单类名
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
            // 最终想生成的类文件名
            String finalClassName = className + "$$ARouter";

            // 公开课写法,也是EventBus写法(https://github.com/greenrobot/EventBus)
            try {
                // 创建一个新的源文件(Class),并返回一个对象以允许写入它
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
                // 定义Writer对象,开启写入
                Writer writer = sourceFile.openWriter();
                // 设置包名
                writer.write("package " + packageName + ";\n");

                writer.write("public class " + finalClassName + " {\n");

                writer.write("public static Class findTargetClass(String path) {\n");

                // 获取类之上@ARouter注解的path值
                ARouter aRouter = element.getAnnotation(ARouter.class);

                writer.write("if (path.equals(\"" + aRouter.path() + "\")) {\n");

                writer.write("return " + className + ".class;\n}\n");

                writer.write("return null;\n");

                writer.write("}\n}");

                // 最后结束别忘了
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return true;
    }

生成的结果在app/build/generted/source/apt/debug/com.netease.apt/MainActivity$$ARouter

Android架构师-组件化-组件化APT介绍与使用 4_第9张图片

文件详情如下:

package com.netease.apt;
public class MainActivity$$ARouter {
public static Class findTargetClass(String path) {
if (path.equals("/app/MainActivity")) {
return MainActivity.class;
}
return null;
}
}

这样我们最后再去做跳转的时候,就可以直接调用了:

比如我们在app下创建两个Activity:然后都在类上添加ARouter注解,运行,生成了$$文件,然后在MainActiity就可以跳转到这两个类了:

    public void jump(View view) {
        Intent intent = new Intent(this, OrderActivity$$ARouter.findTargetClass("/app/OrderActivity"));
        startActivity(intent);
    }

 

运行成功。

 

你可能感兴趣的:(Android架构师-组件化-组件化APT介绍与使用 4)