概念
注解處理器(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ù)代碼的問題,大大提升編碼效率。
例子
butterknife,Dagger2,EventBus......
Annotation Processor實質(zhì)原理
** 編譯期間根據(jù)注解(Annotation)獲取相關(guān)數(shù)據(jù) **
既然Annotation Processor是為了在編譯期間獲取注解(Annotation)相關(guān)內(nèi)容,那么,具體的操作步驟要如何做呢:
- Android Studio創(chuàng)建一個java library
- 自定義一個注解(Annotation),用于存儲元數(shù)據(jù)
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -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;
- 經(jīng)過前面3個步驟后,其實就已經(jīng)算完成了自定義Annotation Processor。后面要做的就是在源碼里面,在需要的地方寫上我們自定義的注解就行了。
Demo
牢記Annotation Process的實質(zhì)用處就是在編譯時通過注解獲取相關(guān)數(shù)據(jù),
那么,在這個Demo里面,我們就直接在編譯時打印出我們注解的數(shù)據(jù)的成員變量名,成員變量類,包裝類類名,包名和注解元數(shù)據(jù)進行顯示,然后將這些信息寫入到一個.java文件中,這里我就簡單的直接輸出這些信息進行顯示。
按照上面自定義注解處理的方法,我們操作如下:
- 創(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'
}
- 自定義一個注解(Annotation),用于存儲元數(shù)據(jù)
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -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,我們可以在編譯時輸出日志. **
- 使用注解,我們在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é)果:
可以看到,我們成功的在編譯期間獲取了我們注解的相關(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')
}