Android组件化APT使用与介绍

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/coderinchina/article/details/96951270

针对上篇讲的module之间的跳转问题, 这就出现了路由的概念了,应该上路由去帮助我们去做那些事

APT(Annatotion Processing Tool)

是一种处理注解的工具,它对源代码文件进行检测找出其中的注解(Annotation)  根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过APT工具进行处理,也可以这么理解, 只有通过APT工具后,程序在编译期间注解解释器才能执行

通俗理解  根据规则  帮我们生成代码  生成类文件

现在动手来实现,新建个项目 APTTest

APT是注解处理器,那么先写个注解,创建一个java library  叫annotation,存放注解的,

package com.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作。注解会在class文件中存在
public @interface ARouter {
    String path();
    String group() default "";
}

annotation library如下目录

然后在app中引入这个library

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

再创建一个java library 注解处理器 compiler 这个是专门处理注解的

扫描二维码关注公众号,回复: 7192427 查看本文章

看下compiler中的build.gradle中配置

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 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')
}
// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
//jdk编译的版本是1.7
sourceCompatibility = "7"
targetCompatibility = "7"

其中

 compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

是Google给我们提供的注解处理器的一种服务,会生成一个文件,在build目录下生成文件

当然上面也引入了annotation 因为要处理这里的注解

现在在compiler中写个注解处理器类ARouterProcessor用来处理ARouter注解的,它继承此AbstractProcessor

在这个自定义的类上要添加几个注解

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理(新增annotation module)
@SupportedAnnotationTypes({"com.example.annotation.ARouter"})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions("content")

代码中使用

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理(新增annotation module)
@SupportedAnnotationTypes({"com.example.annotation.ARouter"})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions("content")
public class ARouterProcessor extends AbstractProcessor {

继承AbstractProcessor后有一个要重写的方法

  @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {

就是初始化 类似Activity的onCreate()方法,然后通过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);
    }

变量的注释如下

// 操作Element工具类 (类、函数、属性都是Element)
    private Elements elementUtils;
    // type(类信息)工具类,包含用于操作TypeMirror的工具方法
    private Types typeUtils;
    // Messager用来报告错误,警告和其他提示信息
    private Messager messager;
    // 文件生成器 类/资源,Filter用来创建新的源文件,class文件以及辅助文件
    private Filer filer;

因为要生成一个class文件比如有包名 类名。方法名。变量其实都封装成了一个对象,因为Java是面向对象的么,其实这里面只要记住一些类 方法什么的,基本都是一样的,上面有一个注解

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

是app的module向compiler 中传递参数,这个怎么弄,通过gradle了,添加如下配置在app的build.gradle

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

看下该类的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);
    }

然后同步,再make app module

看下控制台上的build

从app中传递到compiler中的参数就出来了,如果有乱码的话,在build.gradle中添加

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
ARouterProcessor类中最关键是process()方法,相当于main函数,开始处理注解,注解处理器的核心方法,处理具体的注解,生成Java文件,

参数说明

Set<? extends TypeElement> set   使用了支持处理注解的节点集合(类 上面写了注解)
RoundEnvironment roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解

方法返回值是一个boolean类型,true 表示后续处理器不会再处理(已经处理完成)

看下process()方法的全部代码

@Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) return false;

        // 获取所有带ARouter注解的 类节点
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        // 遍历所有类节点
        for (Element element : elements) {
            // 通过类节点获取包节点(全路径:com.example.annotation)
            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;
    }

其实这里可以模仿EventBus怎么去写的

EventBusAnnotationProcessor这个处理器

 private void createInfoIndexFile(String index) {
        BufferedWriter writer = null;
        try {
            JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
            int period = index.lastIndexOf('.');
            String myPackage = period > 0 ? index.substring(0, period) : null;
            String clazz = index.substring(period + 1);
            writer = new BufferedWriter(sourceFile.openWriter());
            if (myPackage != null) {
                writer.write("package " + myPackage + ";\n\n");
            }
            writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
            writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
            writer.write("import java.util.HashMap;\n");
            writer.write("import java.util.Map;\n\n");
            writer.write("/** This class is generated by EventBus, do not edit. */\n");
            writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
            writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
            writer.write("    static {\n");
            writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
            writeIndexLines(writer, myPackage);
            writer.write("    }\n\n");
            writer.write("    private static void putIndex(SubscriberInfo info) {\n");
            writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
            writer.write("    }\n\n");
            writer.write("    @Override\n");
            writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
            writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
            writer.write("        if (info != null) {\n");
            writer.write("            return info;\n");
            writer.write("        } else {\n");
            writer.write("            return null;\n");
            writer.write("        }\n");
            writer.write("    }\n");
            writer.write("}\n");
        } catch (IOException e) {
            throw new RuntimeException("Could not write source for " + index, e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    //Silent
                }
            }
        }
    }

更高级的写法就要使用到javaPoet,要生成什么文件,最好先创建一个demo类去对照写,不然想很难写的

完成了上面的操作后再make app module 在app中的build目录下

忘记了记得在MainActivity使用这个注解,不使用是不会创建的

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

那我们再新建几个Activity,新建了二个类

package com.example.apttest.order;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import com.example.annotation.ARouter;

@ARouter(path = "/order/MainActivity")
public class OrderActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}
package com.example.apttest.personal;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import com.example.annotation.ARouter;

@ARouter(path = "/personal/MainActivity")
public class PersonalActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

都是在不同的包下, 现在再make app module

apt其实帮你写了一些类,生成了这些类,怎么使用呢?首先看下apt帮我们生成的文件然后再去使用

package com.example.apttest;
public class MainActivity$$ARouter {
public static Class<?> findTargetClass(String path) {
if (path.equals("/app/MainActivity")) {
return MainActivity.class;
}
return null;
}
}

使用

 Intent intent = new Intent(this, PersonalActivity$$ARouter.findTargetClass("/personal/MainActivity"));
        startActivity(intent);

这是module之间交互的最处的写法,因为这方法findTargetClass()方法中还要传递个String参数,这还要处理的.

猜你喜欢

转载自blog.csdn.net/coderinchina/article/details/96951270