架構師學習--組件化開發之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;
}

重新編譯,即可達到同樣的效果。

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