概述
前一篇文章已經整理過註解的一些概念,也是附上了運行時註解的Demo,如果對註解概念不是很熟的讀者建議先看下前一篇文章:android 註解入門(Acitivity路由demo)
此篇文章主要講一下編譯時註解的使用,同時也是以”Activity路由“的Demo爲例子。
本篇的Demo主要是演示了使用編譯時註解來創建文件的功能。
主要模塊
- anotationrouter:創建註解
- processortest:自定義註解解釋器,即實現AbstractProcessor。
- app:使用註解
創建註解
TestRouter
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)//設置爲編譯時生效
public @interface TestRouter {
public String url() default "";
}
RouteManager
public class RouterManager {
public static final String INIT_CLASS = "com.example.generated.TestRouterInit";
public static final String INIT_METHOD = "init";
private HashMap<String, String> map = new HashMap<>();
private static final class Host {
private static final RouterManager instance = new RouterManager();
}
private RouterManager() {
}
public void init() {
try {
Class.forName(INIT_CLASS).getMethod(INIT_METHOD).invoke(null);//調用動態生成的文件
} catch (Exception e) {
e.printStackTrace();
}
}
public static RouterManager getInstance() {
return Host.instance;
}
public void register(String key, String uri) {
if (key != null && uri != null) {
map.put(key, uri);
}
}
public void showAllActivity() {
System.out.println("RouterManager:" + map.toString());
}
}
實現AbstractProcessor
build.gradle(processortest模塊)
主要引入了幾個依賴:
- annotationrouter:用於引入demo中定義的註解
- javapoet:用於動態生成文件
- auto-service:用來生成META-INF/services/javax.annotation.processing.Processor文件
這裏爲什麼auto-service同時需要compileOnly和annotationProcessor?
compileOnly:爲了在源文件中可以使用@AutoService
annotationProcessor:爲了觸發註解解釋器
apply plugin: 'java-library'
dependencies {
implementation project(':annotationrouter')
implementation 'com.squareup:javapoet:1.12.1'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
}
sourceCompatibility = "7"
targetCompatibility = "7"
TestRouterProcessor
這裏一定要實現getSupportedAnnotationTypes(),否則不會觸發process()
新手如果對註解的運行過程不是很瞭解,可以自己查下如何debug AbstractProcessor。操作並不複雜,本文也不多加闡述了。
@AutoService(Processor.class)
public class TestRouterProcessor extends AbstractProcessor {
public static final String ROOT_INIT = "com.example.generated";
public static final String INIT_CLASS = "TestRouterInit";
public static final String INIT_METHOD = "init";
@Override public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
//這個方法非常必要,否則將不會執行到process()方法
@Override public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(TestRouter.class.getCanonicalName());
}
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
System.out.println("TestRouterProcessor process");
if (annotations == null || annotations.isEmpty()) {
return false;
}
for (Element element : env.getElementsAnnotatedWith(TestRouter.class)) {
//獲取註解中的內容
String className = element.getSimpleName().toString();
String uri = element.getAnnotation(TestRouter.class).url();
try {
//使用javapoet來動態生成代碼
MethodSpec main = MethodSpec.methodBuilder(INIT_METHOD)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addStatement("$T.getInstance().register($S,$S)", RouterManager.class, className, uri)
.build();
TypeSpec testRouterInit = TypeSpec.classBuilder(INIT_CLASS)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder(ROOT_INIT, testRouterInit)
.build();
Filer filer = processingEnv.getFiler();
javaFile.writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
}
使用註解
build.gradle(app模塊)
主要引用了兩個模塊:
- annotationrouter:引用註解模塊,這樣可以再Activity上使用TestRouter
- processortest:使用註解解釋器觸發processortest模塊,編譯結束後可以動態生成TestRouterInit文件。
apply plugin: 'com.android.application'
——中間代碼省略,都是自動生成的——
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation project(':annotationrouter')
annotationProcessor project(':processortest')
}
MainActivity
@TestRouter(url = "scheme://test")//使用註解
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RouterManager.getInstance().init();//初始化註解,init中調用的代碼是動態生成的
RouterManager.getInstance().showAllActivity();//show目前註冊的
}
}
運行成功後的log
com.example.annotaiontest I/System.out: RouterManager:{MainActivity=scheme://test}