注解處理器(Annotation Processor)簡析

概念

注解處理器(Annotation Processor)是javac內(nèi)置的一個用于編譯時掃描和處理注解(Annotation)的工具。簡單的說,在源代碼編譯階段,通過注解處理器,我們可以獲取源文件內(nèi)注解(Annotation)相關(guān)內(nèi)容。

用途

由于注解處理器可以在程序編譯階段工作,所以我們可以在編譯期間通過注解處理器進行我們需要的操作。比較常用的用法就是在編譯期間獲取相關(guān)注解數(shù)據(jù),然后動態(tài)生成.java源文件(讓機器幫我們寫代碼),通常是自動產(chǎn)生一些有規(guī)律性的重復(fù)代碼,解決了手工編寫重復(fù)代碼的問題,大大提升編碼效率。

例子

butterknifeDagger2,EventBus......

Annotation Processor實質(zhì)原理

** 編譯期間根據(jù)注解(Annotation)獲取相關(guān)數(shù)據(jù) **

既然Annotation Processor是為了在編譯期間獲取注解(Annotation)相關(guān)內(nèi)容,那么,具體的操作步驟要如何做呢:

  1. Android Studio創(chuàng)建一個java library
  2. 自定義一個注解(Annotation),用于存儲元數(shù)據(jù)
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default -1;
}
  1. 創(chuàng)建一個自定義Annotation Processor繼承于AbstractProcessor
package com.example;

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env){
    }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv) { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { 
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
    }
}
  • @AutoService(Processor.class) :向javac注冊我們這個自定義的注解處理器,這樣,在javac編譯時,才會調(diào)用到我們這個自定義的注解處理器方法。
    AutoService這里主要是用來生成
    META-INF/services/javax.annotation.processing.Processor文件的。如果不加上這個注解,那么,你需要自己進行手動配置進行注冊,具體手動注冊方法如下:
    1.創(chuàng)建一個
    META-INF/services/javax.annotation.processing.Processor文件,
    其內(nèi)容是一系列的自定義注解處理器完整有效類名集合,以換行切割:
com.example.MyProcessor
com.foo.OtherProcessor
net.blabla.SpecialProcessor

2.將自定義注解處理器和
META-INF/services/javax.annotation.processing.Processor打包成一個.jar文件。所以其目錄結(jié)構(gòu)大概如下所示:

MyProcessor.jar
    - com
        - example
            - MyProcessor.class

    - META-INF
        - services
            - javax.annotation.processing.Processor

*** 建議直接采用@AutoService(Processor.class)進行自定義注解處理器注冊,簡潔方便 ***

  • init(ProcessingEnvironment env):每個Annotation Processor必須***
    有一個空的構(gòu)造函數(shù) *。編譯期間,init()會自動被注解處理工具調(diào)用,并傳入ProcessingEnviroment參數(shù),通過該參數(shù)可以獲取到很多有用的工具類: Elements , Types , Filer **等等
  • process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv):Annotation Processor掃描出的結(jié)果會存儲進roundEnv中,可以在這里獲取到注解內(nèi)容,編寫你的操作邏輯。注意,process()函數(shù)中不能直接進行異常拋出,否則的話,運行Annotation Processor的進程會異常崩潰,然后彈出一大堆讓人捉摸不清的堆棧調(diào)用日志顯示.
  • getSupportedAnnotationTypes(): 該函數(shù)用于指定該自定義注解處理器(Annotation Processor)是注冊給哪些注解的(Annotation),注解(Annotation)指定必須是完整的包名+類名(eg:com.example.MyAnnotation)
  • getSupportedSourceVersion():用于指定你的java版本,一般返回:SourceVersion.latestSupported()。當(dāng)然,你也可以指定具體java版本:
    return SourceVersion.RELEASE_7;
  1. 經(jīng)過前面3個步驟后,其實就已經(jīng)算完成了自定義Annotation Processor。后面要做的就是在源碼里面,在需要的地方寫上我們自定義的注解就行了。

Demo

牢記Annotation Process的實質(zhì)用處就是在編譯時通過注解獲取相關(guān)數(shù)據(jù),
那么,在這個Demo里面,我們就直接在編譯時打印出我們注解的數(shù)據(jù)的成員變量名,成員變量類,包裝類類名,包名和注解元數(shù)據(jù)進行顯示,然后將這些信息寫入到一個.java文件中,這里我就簡單的直接輸出這些信息進行顯示。
按照上面自定義注解處理的方法,我們操作如下:

  1. 創(chuàng)建一個java library,其gradle配置如下:
apply plugin: 'java'

targetCompatibility = '1.7'
sourceCompatibility = '1.7'

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.google.auto.service:auto-service:1.0-rc3'
}
  1. 自定義一個注解(Annotation),用于存儲元數(shù)據(jù)
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default -1;
}
  1. 創(chuàng)建一個自定義Annotation Processor繼承于AbstractProcessor


@AutoService(Processor.class)
public class MyAnnotationProcessor extends AbstractProcessor {

    private Filer mFiler;
    private Messager mMessager;
    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mMessager = processingEnvironment.getMessager();
        mElementUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for (Element element : bindViewElements) {
            //1.獲取包名
            PackageElement packageElement = mElementUtils.getPackageOf(element);
            String pkName = packageElement.getQualifiedName().toString();
            note(String.format("package = %s", pkName));

            //2.獲取包裝類類型
            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
            String enclosingName = enclosingElement.getQualifiedName().toString();
            note(String.format("enclosindClass = %s", enclosingElement));


            //因為BindView只作用于filed,所以這里可直接進行強轉(zhuǎn)
            VariableElement bindViewElement = (VariableElement) element;
            //3.獲取注解的成員變量名
            String bindViewFiledName = bindViewElement.getSimpleName().toString();
            //3.獲取注解的成員變量類型
            String bindViewFiledClassType = bindViewElement.asType().toString();

            //4.獲取注解元數(shù)據(jù)
            BindView bindView = element.getAnnotation(BindView.class);
            int id = bindView.value();
            note(String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id));

            //4.生成文件
            createFile(enclosingElement, bindViewFiledClassType, bindViewFiledName, id);
            return true;
        }
        return false;
    }

    private void createFile(TypeElement enclosingElement, String bindViewFiledClassType, String bindViewFiledName, int id) {
        String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
        try {
            JavaFileObject jfo = mFiler.createSourceFile(pkName + ".ViewBinding", new Element[]{});
            Writer writer = jfo.openWriter();
            writer.write(brewCode(pkName, bindViewFiledClassType, bindViewFiledName, id));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private String brewCode(String pkName, String bindViewFiledClassType, String bindViewFiledName, int id) {
        StringBuilder builder = new StringBuilder();
        builder.append("package " + pkName + ";\n\n");
        builder.append("http://Auto generated by apt,do not modify!!\n\n");
        builder.append("public class ViewBinding { \n\n");
        builder.append("public static void main(String[] args){ \n");
        String info = String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id);
        builder.append("System.out.println(\"" + info + "\");\n");
        builder.append("}\n");
        builder.append("}");
        return builder.toString();
    }


    private void note(String msg) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
    }

    private void note(String format, Object... args) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args));
    }

}

** 借助Messager,我們可以在編譯時輸出日志. **

  1. 使用注解,我們在Android工程中創(chuàng)建幾個測試類,然后進行注解,如下所示:
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

rebuild一下,可以在Gradle Console窗口中看到打印結(jié)果:


result

可以看到,我們成功的在編譯期間獲取了我們注解的相關(guān)數(shù)據(jù)。只要拿到了數(shù)據(jù),那么你自己想干嘛就自己去弄吧 _
最后,我們根據(jù)注解獲取到的數(shù)據(jù)還生成了一個java文件,其生成路徑:app\build\generated\source\apt\debug\com\yn\annotationprocessdemo\ViewBinding.java
具體內(nèi)容如下:

package com.yn.annotationprocessdemo;

//Auto generated by apt,do not modify!!

public class ViewBinding {

    public static void main(String[] args) {
        System.out.println("android.widget.TextView tv = 2131427422");
    }
}

附錄:

  • @AutoService引入:
compile 'com.google.auto.service:auto-service:1.0-rc3'
  • app的gralde配置:
      apply plugin: 'com.android.application'

      android {
          compileSdkVersion 24
          buildToolsVersion "24.0.0"

          defaultConfig {
              applicationId "com.example.annotationprocessor"
              minSdkVersion 15
              targetSdkVersion 24
              versionCode 1
              versionName "1.0"
          }
           buildTypes {
              release {
                  minifyEnabled false
                  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
              }
          }

          compileOptions {
              sourceCompatibility JavaVersion.VERSION_1_7
              targetCompatibility JavaVersion.VERSION_1_7
          }
          //解決duplicate問題
          packagingOptions {
              exclude 'META-INF/services/javax.annotation.processing.Processor'
          }
          }
          dependencies {
          compile fileTree(dir: 'libs', include: ['*.jar'])
          testCompile 'junit:junit:4.12'
          compile 'com.android.support:appcompat-v7:24.0.0'
          compile project(path: ':annotationprocessor')
          }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容