上一篇文章完成了組件化工程的搭建(https://www.jianshu.com/p/ed2c9f677e0e),這篇文章來說下APT和JavaPoet
下一篇我們會說下ARouter是如何實現頁面跳轉的(https://www.jianshu.com/p/0fa14358a765)
APT
APT是英文Annotation Processing Tool的簡寫,是一種在編譯期間通過對註解的處理,來實現自動生成輔助代碼的技術。生成代碼的方式有直接拼接字符串這種比較原始的方式,還有一種面向對象編程的方式-JavaPoet。使用APT技術的框架,通常都是屬於編譯期框架,如組件化框架ARouter,通信框架EventBus,以及使用非常頻繁的ButterKnife,他們都會使用到APT並藉助它來實現部分代碼的生成,就等於藉助編譯器幫我們寫代碼一樣。
JavaPoet
JavaPoet (https://github.com/square/javapoet)簡單理解就是一個用來生成 .java源文件的Java API。我們通常寫代碼都是自己創建一個類,然後自己編寫代碼的實現,但是JavaPoet卻是一個可以幫我們寫java文件的庫,由square開源。早期的APT技術生成輔助類的方式可以參考下EventBus中的實現,是通過拼接大量的字符串來生成一個文件,非常的繁瑣,並且容易出錯,JavaPoet的出現解決了這個問題,讓我們以一種面向對象的方式生成java文件。實現方式可以參考ARouter源碼。
如何使用
那麼在Android開發中如何去使用APT技術,並結合JavaPoet去生成輔助類呢,其實很簡單,今天的主要內容就是通過JavaPoet生成一個HelloWorld.java類
1.創建工程
我們創建一個Android項目,然後創建一個java library, apt技術需要在java library中創建相關類。然後讓app模塊依賴這個library,暫且命名爲hello-world-compiler
然後引入需要的一些依賴,在java library模塊的build.gradle文件的根目錄配置如下
dependencies {
//auto-service是Google開源的一個庫,可以方便快捷的幫助我們進行組件化開發
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.9.0"
}
既然是APT技術,那麼必然少不了註解的,我們仿照開源框架的方式,再創建一個java library,命名爲hello-world-annotion,然後在裏邊創建一個註解,就叫HelloWorld,然後在上邊的hello-world-compiler中依賴它,這樣,我們的註解處理器就可以找到這個註解了
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface HelloWorld {
//這裏也可以設置一些東西
}
dependencies {
//auto-service是Google開源的一個庫,可以方便快捷的幫助我們進行組件化開發
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.9.0"
implementation project(":hello-world-annotion")
}
那麼當前三個module的依賴關係如下:
app
implementation project(":hello-world-annotation")
annotationProcessor project(":hello-world-compiler")
hello-world-compiler:
implementation project(":hello-world-annotation")
2.編寫註解處理器
現在開始寫註解處理器,創建一個類MyCompiler,MyCompiler繼承AbstractProcessor,實現它的process方法
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
我們上邊知道,app以annotationProcessor的方式依賴了hello-world-compiler,所以當app模塊編譯的時候,process方法會被執行。或者如果有很多的module,每個module都依賴了註解處理器hello-world-compiler,那麼每個module編譯的時候,都會單獨走一次process方法
除了這個必須實現的process方法之外,我們再重寫AbstractProcessor的幾個方法,他們的含義見註釋,如下
@AutoService(Processor.class)
public class HelloWorldProcessor extends AbstractProcessor {
// type(類信息)的工具類,包含用於操作TypeMirror的工具方法
private Types typeUtils;
// Message用來打印 日誌相關信息
private Messager messager;
// 文件生成器, 類 資源 等,就是最終要生成的文件 是需要Filer來完成的
private Filer filer;
// 操作Element的工具類(類,函數,屬性,其實都是Element)
private Elements elementUtils;
//從每個module接收到的數據
private String moduleName;
//此方法用於指定可以從module中接收的數據
@Override
public Set<String> getSupportedOptions() {
return super.getSupportedOptions();
}
//此方法指明註解處理器支持的java sdk版本,建議和項目保持一致,否則編譯時會有提示
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
//指明註解處理器可以處理哪些註解,如我們本次只有HelloWorld這個註解、
//所以把它添加到set中並返回,表示我們只處理HelloWorld
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new HashSet<>();
set.add(HelloWorld.class.getName());
return set;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
//如我們從app的build.gradle中配置了一個key爲moduleName的值
//在這裏可以接收到
moduleName = processingEnvironment.getOptions().get(“moduleName”);
}
....
}
向註解處理器傳遞參數的配置方式
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
// project.getName() == app
arguments = [moduleName: project.getName()]
}
}
}
...
}
3.JavaPoet api
我們知道JavaPoet是用來寫java文件的,一個java文件通常對應一個java類,那麼java類中的元素在JavaPoet中都有所對應
方法 ---- MethodSpec
class ---- TypeSpec
java文件 ---- JavaFile
那麼我們最終要生成一個這樣的文件,代碼該怎麼寫?
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
直接貼上process方法的實現
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) {
messager.printMessage(Diagnostic.Kind.NOTE, "並沒有發現 被@HelloWorld註解的地方呀");
return false; // 沒有機會處理
}
// 獲取所有被 @ HelloWorld 註解的 元素集合
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(HelloWorld.class);
// 遍歷所有的類節點
for (Element element : elements) {
// 獲取簡單類名,例如:MainActivity
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被@ HelloWorld註解的類有:" + className); // 打印出 就證明APT沒有問題
// 1.方法
MethodSpec mainMethod = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
// 2.類
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(mainMethod)
.build();
// 3.包
JavaFile packagef = JavaFile.builder("com.renzhenming", helloWorld).build();
// 去生成
try {
packagef.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "生成失敗,請檢查代碼...");
}
}
return true; 表示處理註解完成
}
這樣一來,在app模塊中使用HelloWorld註解某一個類,編譯後就會在代碼中生成一個HelloWorld的類了