架构师学习--组件化开发之APT使用及JavaPoet

一、概念及作用

什么是APT?全称是Annotation Processing Tool,翻译过来就是注解处理工具,它的作用就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入,因此它能够使我们编写的代码更加优雅。目前很多优秀的第三方库就是使用APT的技术,比如butterknife、retrofit、enentBus等。

二、使用

1、在当前工程中创建注解的java library,命名为annotation

创建注解类,命名ARouter.java。

/**
 * <pre>
 *     author  : QB
 *     time    : 2019/7/15
 *     version : v1.0.0
 *     desc    : 注解文件,提供path参数
 * </pre>
 */
@Target(ElementType.TYPE)// 该注解作用在类之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface ARouter {
    String path();
}

2、在当前工程中创建注解处理器的java library,命名为compiler
(1)添加依赖

在当前library的build.gradle中添加如下依赖:


    // AS3.4.1 + Gradle5.1.1 + 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'
    
    //javaPoet依赖
    implementation 'com.squareup:javapoet:1.11.1'
    
    // 引入annotation,处理@ARouter注解
    implementation project(':annotation')
(2)创建注解处理器类

创建ARouterCompiler.java类,继承至AbstractProcessor,代码如下:

/**
 * <pre>
 *     author  : QB
 *     time    : 2019/7/15
 *     version : v1.0.0
 *     desc    : 采用javaPoet生成文件
 * </pre>
 */
//使用AutoService生成注解处理器
@AutoService(Processor.class)
//当前注解类型
@SupportedAnnotationTypes({"com.xinyartech.annotation.ARouter"})
//JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//接收参数
@SupportedOptions("content")
public class ARouterCompiler extends AbstractProcessor {

    //节点信息
    private Elements elementUtils;
    //文件生成器
    private Filer mFiler;
    //日志打印
    private Messager mMessager;
    private Types typeUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        mFiler = processingEnvironment.getFiler();
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();

        mMessager.printMessage(Diagnostic.Kind.NOTE, processingEnvironment.getOptions().get(
                "content"));

    }

    @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) {
            //获取完整包名
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            //获取类名
            String className = element.getSimpleName().toString();

            //打印当前类信息
            mMessager.printMessage(Diagnostic.Kind.NOTE, "当前ARouter注解的类:" + className);

            //最终编译生成的java文件
            String finalClassName = className + "$$ARouter";

            //拿到当前类的注解
            ARouter aRouter = element.getAnnotation(ARouter.class);
            String path = aRouter.path();
			
			//采用拼接的方式生成注解文件
	    try {
                //创建java文件 用于处理业务代码
                JavaFileObject sourceFile =  filer.createSourceFile(packageName + "." + finalClassName);
                //返回可操作java文件的对象
                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 aRouter = element.getAnnotation(ARouter.class);
                //拿到当前注解的路径
                String aRouterPath = aRouter.path();
                //以下为方法逻辑
                writer.write("if(path.equalsIgnoreCase(\"" + aRouterPath + "\")){\n");
                writer.write("return " + className + ".class;\n}\n");
                writer.write("return null;\n");
                writer.write("}\n}");

                //切记不要忘记关闭write
                writer.close();
                
            } catch (IOException e) {
                e.printStackTrace();
            }

        return true;
    }
}
3、主项目app
(1)添加依赖

在app的build.gradle中添加如下依赖

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

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

如果需要传递参数给注解处理器,比如我们在注解处理器接受的key为"content",那么可以在app的build.gradle配置传递的值,代码如下:

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

切记,一定要在defaultConfig节点下配置。

(2)创建MainActivity.java
@ARouter(path = "/main/MainActivity")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void jump(View view) {
        startActivity(new Intent(this, SecondActivity$$ARouter.getTargetClass("/main" +
                "/SecondActivity")));
    }
}

(3)创建SecondActivity.java
@ARouter(path = "/main/SecondActivity")
public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    }
}

在类上添加ARouter注解,此时编译项目,会在build目录下生成目标文件。截图如下:
在这里插入图片描述
这样就可以像代码中那样实现跳转了。

二、JavaPoet

1、概念

JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

2、使用
(1)需要在注解处理器的libary即compiler中build.config中加入依赖
//javaPoet依赖
implementation 'com.squareup:javapoet:1.11.1'

(2)修改ARouterCompiler.java类中代码生成方式

主要的代码在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) {
        //获取完整包名
        String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
        //获取类名
        String className = element.getSimpleName().toString();

        //打印当前类信息
        mMessager.printMessage(Diagnostic.Kind.NOTE, "当前ARouter注解的类:" + className);

        //最终编译生成的java文件
        String finalClassName = className + "$$ARouter";

        //拿到当前类的注解
        ARouter aRouter = element.getAnnotation(ARouter.class);
        String path = aRouter.path();


        //文件处理器编写代码 ,采用javaPoet方式 先写方法,在写类,最后写包 method->class->package

        //构建方法 $T:代表类 $S:代表字符串
        MethodSpec methodSpec = MethodSpec.methodBuilder("getTargetClass")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(Class.class)
                .addParameter(String.class, "path")
                .addStatement("return path.equalsIgnoreCase($S) ? " +
                        "$T.class : null", path, ClassName.get((TypeElement) element))
                .build();

        //构建类
        TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(methodSpec)
                .build();

        //构建包
        JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                .build();


        //写入到文件生成器
        try {
            javaFile.writeTo(mFiler);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    return true;
}

重新编译,即可达到同样的效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章