Android組件化實戰五: APT的高級用法JavaPoet

前言

上一篇文章簡單介紹了APT及其使用,生成相應的java文件,幫我們執行相關的操作,生成java文件的方式是字符串拼接的方式,但是如果要生成的java文件成員屬性和方法比較多,這種方式就比較麻煩了,而且容易出現人爲失誤。所有就有了API調用的方式生成java文件,也就是JavaPoet。

什麼是JavaPoet

項目主頁及源碼:https://github.com/square/javapoet

APT + JavaPoet = 超級利刃

JavaPoet是square推出的開源java代碼生成框架,提供Java Api生成.java源文件,這個框架功能非常實用,也是我們習慣的Java面向對象OOP語法,可以很方便的使用它根據註解生成相應的代碼,通過這種自動化生成代碼的方式,可以讓我們更加簡潔優雅的方式要替代繁瑣冗雜的重複工作(常規的通過繼承註解處理器AbstractProcessor,根據註解生成java代碼的時候,需要一行一行的進行字符串的拼接,如果生成java文件內容較多,實在是不優雅,可以說是不人性)。

結構體語言

Android Studio 3.4.2 + Gradle5.1.1 (向下兼容)

compileOnly ‘com.google.auto.service:auto-service:1.0-rc4’
annotationProcessor ‘com.google.auto.service:auto-service:1.0-rc4’

依賴

//幫助我們通過類調用的形式來生成java代碼

implementation ‘com.squareup:javapoet:1.10.0’

JavaPoet的8個常用類

注意與APT中結構體語言表述區別

類對象 說明
MethodSpec 代表一個構造函數或方法說明
TypeSpec 代表一個類、接口或者枚舉聲明
FieldSpec 代表一個成員變量,一個字段聲明
JavaFile 包含一個頂級類的java文件
ParameterSpec 用來創建參數
AnnotationSpec 用來創建註解
ClassName 用來包裝一個類
TypeName 類型,如在添加返回值類型時使用TypeName.VOID

JavaPoet字符串格式化規則

在這裏插入圖片描述

核心原理

// 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 {

    // 操作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(app module)傳過來的參數
        String content = processingEnvironment.getOptions().get("content");
        // 有坑:Diagnostic.Kind.ERROR,異常會自動結束,不像安卓中Log.e那麼好使
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        if (set.isEmpty()) return false;
        //獲取所有被@QRouter註解的 類節點
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ARouter.class);
        //遍歷
        for (Element element : elements) {
            //類節點的上一節點,包節點(獲取包名)
            String packageName =
            elementUtils.getPackageOf(element).getQualifiedName().toString();
            //獲取簡單類名(不帶包名)
            String className = element.getSimpleName().toString();
            //打印類名信息
            messager.printMessage(Diagnostic.Kind.NOTE, "被@ARouter註解的類有:" + className);
            //最終生成的類文件名
            String finalClassName = className + "$ARouter";
            //獲取註解
            ARouter aRouter = element.getAnnotation(ARouter.class);

            //構建方法體

            //public static Class<?> findTargetClass(String path) {}
            MethodSpec methodSpec = MethodSpec.methodBuilder("findTargetClass")//方法名
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(Class.class)//返回值Class<?>
                    .addParameter(String.class, "path")//參數(String path)
                    //方法內容拼接:
                    //return path.equals("app/MainActivity") ? MainActivity.class : null;
                    .addStatement("return path.equals($S) ? $T.class : null",
                            aRouter.path(),//$S 參數值, 字符串 /app/MainActivity
                            ClassName.get((TypeElement) element))//$T 類名
                    .build();//構建
            //構建類
            TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(methodSpec)//添加方法體
                    .build();//構建

            //生成文件
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .build();

            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return true;
    }
}

build項目,生成的java文件如下:

public final class MainActivity$ARouter {
    public static Class findTargetClass(String path) {
        return path.equals("/app/MainActivity") ? MainActivity.class : null;
    }
}

可對比上一篇文章字符串拼接的方式生成java文件。
代碼鏈接:https://github.com/xpf-android/Moduar_JavaPoet

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