本文同步至:http://blog.edreamoon.com/
注解的生命周期
注解生命周期即表明注解在代碼的什么階段生效,通過@Retention來指定,其值可以為以下三種:
- SOURCE,源碼注解, 注解只在源碼中存在,javac在編譯成class時會把Java源程序上的源碼注解給去掉編譯成.class文件就將相應的注解去掉。
- CLASS,編譯時注解,注解在源碼、.class文件里面存在。
- RUNTIME,運行時注解,在運行階段還起作用
三個階段簡單表示為:java源文件–>class文件–>內存中的字節碼
注解處理器(Annotation Processor)
注解處理器用來在編譯時掃描和處理注解,是javac的一個工具。一般使用注解處理器在編譯時生成新的java文件,新生成的java文件會在編譯期同其它java文件一樣被編譯。
目前比較流行的一些庫都用到了注解處理器,如dagger2、butterknife、Android提供的data binding,相對于使用反射極大的提高了效率。
案例
Talk is cheap, show me the code! 下面使用一個demo來說明如何使用注解處理器。這個Demo比較簡單,只是在編譯項目時,打印注解信息。
工程目錄如下:
mj-annotate、mj-processor是java library,分別用于自定義注解、自定義注解處理器。
- 首先定義注解,這個很簡單
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface BindTest {
int value();
}
這里指定為源碼注解,可以用到字段、方法、類上
- 在項目的Activity中使用此注解,在類、方法、字段中均使用了下。
@BindTest(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@BindTest(R.id.bt_activity_main)
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@BindTest(R.id.bt_activity_main)
public void onClickButton() {
}
}
3.注解已經使用了,但這些注解并沒有起到什么作用。如何讓項目編譯時處理這些注解并打印注解相關信息哪?接下來就是注解處理器顯神威的時候了。
定義一個處理器需要繼承AbstractProcessor,并覆蓋其中的一些方法的實現。
public class MProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
/**
* 處理注解的地方,注解的上面說到的生成Java文件就是在此處理的,這里可以獲取注解相關的內容
*/
Messager messager = processingEnv.getMessager();
//遍歷用BindView注解的元素
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindTest.class)) {
BindView annotation = element.getAnnotation(BindView.class);
int value = annotation.value(); //注解的值
ElementKind kind = element.getKind();//注解作用的位置
if (kind == ElementKind.FIELD) {//如果這個元素是一個方法
VariableElement variableElement = (VariableElement) element;//轉換成方法對應的element
messager.printMessage(Diagnostic.Kind.NOTE, "field");
} else if (kind == ElementKind.METHOD) {//如果這個元素是一個方法
ExecutableElement executableElement = (ExecutableElement) element;//轉成方法對應的element
messager.printMessage(Diagnostic.Kind.NOTE, "method return type : " + executableElement.getReturnType().toString());
List<? extends VariableElement> params = executableElement.getParameters();//獲取方法所有的參數
} else if (kind == ElementKind.CLASS) {
TypeElement typeElement = (TypeElement) element;
messager.printMessage(Diagnostic.Kind.NOTE, "class " + typeElement.getQualifiedName());
}
//打印注解相關的信息
messager.printMessage(Diagnostic.Kind.NOTE, value + " : " + kind.name());
}
/**
* 返回true表示該注解已經被處理, 后續不會再有其他處理器處理; 返回false表示仍可被其他處理器處理.
*/
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
/**
* 返回支持的注解類型,限定注解處理器用到哪些注解上
*/
Set<String> types = new LinkedHashSet<>();
types.add(BindTest.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
/**
*指定使用的Java版本,為了更好的兼容使用SourceVersion.latestSupported()
*/
return SourceVersion.latestSupported();
}
}
主要實現就是getSupportedAnnotationTypes、getSupportedSourceVersion、process,代碼中已經解釋很清楚了。其中針對AbstractProcessor,也可以使用@SupportedAnnotationTypes、@SupportedSourceVersion注解來代替getSupportedAnnotationTypes() 、getSupportedSourceVersion()。也就是代碼簡化為如下:
@SupportedAnnotationTypes({"com.mj.annotate.BindView"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//..............省略..........
}
}
但考慮到兼容性和可維護性,最好還是用覆蓋對應方法的形式實現比較好。
處理器已經寫好了,然后在 app module 中添加對 mj-processor 的依賴。
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:25.1.0'
compile project(path: ':mj-annotate')
compile project(path: ':mj-processor')
}
此時Rebuild下項目,并不會打印process中的log信息,還需要創建處理器說明文件: main->resources->META-INF/services->javax.annotation.processing.Processor,然后在文件中輸入定義的處理器:com.mj.processor.MProcessor。如果有多個Processor,以換行切換。這個步驟要創建多級目錄比較麻煩,我們可以使用AutoService注解簡化,在mj-processor 中添加相應依賴即可使用:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(path: ':mj-annotate')
compile 'com.google.auto.service:auto-service:1.0-rc2'
}
在 MProcessor 類使用@AutoService(Processor.class),編譯成jar包時即會自動生成上述文件。
此時再回過頭看文章開頭的目錄結構,就和各步驟對應了。
此時再Rebuild下項目就會打印log:
注意,此處是log是在gradle console中,為了能看到log,最好是Rebuild,或者是clean下項目。
Processor運行環境
既然Processor是編譯時處理各種注解的,而Processor也是java文件,那它是如何運行工作的哪?
Processor也是運行在虛擬機JVM中的,只不過是一個獨立的JVM,javac會啟動一個完整Java虛擬機來保證Processor的工作,所以在這里保證了java的運行環境。
APT
Processor是在編譯期工作的,而我們的apk運行時就不再需要了,但反編譯上面生成的apk會發現mj-processor的代碼。
APT(Annotation Processing Tool)就很好解決了這個問題,它能在編譯時期去依賴注解處理器并進行工作,但在生成 APK 時不會包含任何遺留的東西
修改工程目錄下的 build.gradle,添加maven倉庫、apt依賴
repositories {
jcenter()
mavenCentral() //for apt
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //for apt
}
然后在 app module 修改依賴:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:25.1.0'
compile project(':mj-annotate')
// compile project(':mj-processor')
apt project(':mj-processor') // 使用apt依賴
}
這樣就可以去除apk中processor相關內容