編譯時註解 AbstractProcessor (Activity路由Demo)

概述

前一篇文章已經整理過註解的一些概念,也是附上了運行時註解的Demo,如果對註解概念不是很熟的讀者建議先看下前一篇文章:android 註解入門(Acitivity路由demo)

此篇文章主要講一下編譯時註解的使用,同時也是以”Activity路由“的Demo爲例子。

本篇的Demo主要是演示了使用編譯時註解來創建文件的功能。

主要模塊

  1. anotationrouter:創建註解
  2. processortest:自定義註解解釋器,即實現AbstractProcessor。
  3. 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模塊)

主要引用了兩個模塊:

  1. annotationrouter:引用註解模塊,這樣可以再Activity上使用TestRouter
  2. 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}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章