Android編譯時注解
[TOC]
前言
相信大家經(jīng)常都使用到注解冯勉,如果使用過AndroidAnnotations,Dagger2,EventBus,RxJava,BufferKnife等開源項目,對注解應該更為深刻盔几,這些項目的原理基本都是基于編譯時注解動態(tài)生成代碼,效果等同于手寫代碼掩幢,因此相對反射來說性能基本無影響逊拍。
另外,已經(jīng)實現(xiàn)了注解輕松實現(xiàn)線程切換開源項目际邻,歡迎fork&star.
了解注解
注解的概念
注解(Annotation)芯丧,也叫元數(shù)據(jù)(Metadata),是Java5的新特性世曾,JDK5引入了Metadata很容易的就能夠調(diào)用Annotations缨恒。注解與類、接口度硝、枚舉在同一個層次肿轨,并可以應用于包、類型蕊程、構(gòu)造方法、方法驼唱、成員變量藻茂、參數(shù)、本地變量的聲明中玫恳,用來對這些元素進行說明注釋辨赐。
注解的語法與定義
- 以@interface關(guān)鍵字定義
- 注解可以包含成員,成員以無參數(shù)的方法的形式被聲明京办,其方法名和返回值定義了該成員的名字和類型掀序。
- 成員賦值是通過@Annotation(name=value)的形式。
- 注解需要標明注解的生命周期惭婿,注解的修飾目標等信息不恭,這些信息是通過元注解實現(xiàn)叶雹。
以 java.lang.annotation 中定義的 Target 注解為例:
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE } )
public @interface Target {
ElementType[] value();
}
源碼分析如下:
第一:元注解@Retention,成員value的值為RetentionPolicy.RUNTIME换吧。
第二:元注解@Target折晦,成員value是個數(shù)組,用{}形式賦值沾瓦,值為ElementType.ANNOTATION_TYPE
第三:成員名稱為value满着,類型為ElementType[]
另外,需要注意一下贯莺,如果成員名稱是value风喇,在賦值過程中可以簡寫。如果成員類型為數(shù)組缕探,但是只賦值一個元素魂莫,則也可以簡寫。如上面的簡寫形式為:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
注解的分類
1 基本內(nèi)置注解撕蔼,是指Java自帶的幾個Annotation豁鲤,如@Override、Deprecated鲸沮、@SuppressWarnings等琳骡;
2 元注解(meta-annotation),是指負責注解其他注解的注解讼溺,JDK 1.5及以后版本定義了4個標準的元注解類型楣号,如下:
@Target
@Retention
@Documented
@Inherited
3 自定義注解,根據(jù)需要可以自定義注解怒坯,自定義注解需要用到上面的meta-annotation
元注解
- Java定義了4個標準的元注解( <span style="color:#F00">java8之后新增了@Repeatable元注解</span>):
- @Documented:標記注解炫狱,注解表明這個注解應該被 javadoc工具記錄. 默認情況下,javadoc是不包括注解的. 但如果聲明注解時指定了 @Documented,則它會被 javadoc 之類的工具處理, 所以注解類型信息也會被包括在生成的文檔中。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
- @Inherited:標記注解剔猿,允許子類繼承父類的注解视译,此注解理解有難度,可以參考這里归敬,總的來說就是子類在繼承父類時如果父類上的注解有此@Inherited標記酷含,那么子類就能把父類的這個注解繼承下來,如果沒有@Inherited標記汪茧,那么子類在繼承父類之后并沒有繼承父類的注解(不知道有沒有說明白了椅亚,不明白就還是點進鏈接去看下吧)。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
- @Retention:指Annotation被保留的時間長短舱污,標明注解的生命周期
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
注解需要標明注解的生命周期呀舔,這些信息是通過元注解 @Retention 實現(xiàn),注解的值是 enum 類型的 RetentionPolicy扩灯,包括以下幾種情況:
public enum RetentionPolicy {
/**
* 注解只保留在源文件媚赖,當Java文件編譯成class文件的時候霜瘪,注解被遺棄.
* 這意味著注解僅存在于編譯器處理期間,編譯器處理完之后省古,該注解就沒用了粥庄,在class文件找不到了
*/
SOURCE,
/**
* 注解被保留到class文件,但jvm加載class文件時候被遺棄豺妓,這是默認的生命周期.
* 簡單來說就是你在class文件中還能看到注解
*/
CLASS,
/**
* 注解不僅被保存到class文件中惜互,jvm加載class文件之后,仍然存在琳拭,
* 保存到class對象中训堆,可以通過反射來獲取
*/
RUNTIME
}
- @Target:標明注解的修飾目標( <span style="color:#F00">java8為ElementType枚舉增加了TYPE_PARAMETER、TYPE_USE兩個枚舉值</span>)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
// ElementType取值
public enum ElementType {
/** 類白嘁、接口(包括注解類型)或枚舉 */
TYPE,
/** field屬性坑鱼,也包括enum常量使用的注解 */
FIELD,
/** 方法 */
METHOD,
/** 參數(shù) */
PARAMETER,
/** 構(gòu)造函數(shù) */
CONSTRUCTOR,
/** 局部變量 */
LOCAL_VARIABLE,
/** 注解上使用的元注解 */
ANNOTATION_TYPE,
/** 包 */
PACKAGE
}
注解處理器(Annotation Processor)
概述
注解處理器是javac的一個工具,它用來在編譯時掃描和處理注解(Annotation)絮缅。你可以自定義注解鲁沥,并注冊到相應的注解處理器,由注解處理器來處理你的注解耕魄。一個注解的注解處理器画恰,以Java代碼(或者編譯過的字節(jié)碼)作為輸入,生成文件(通常是.java文件)作為輸出吸奴。這些生成的Java代碼是在生成的.java文件中允扇,所以你不能修改已經(jīng)存在的Java類,例如向已有的類中添加方法则奥。這些生成的Java文件考润,會同其他普通的手動編寫的Java源代碼一樣被javac編譯。
簡單來說读处,在源代碼編譯階段糊治,通過注解處理器,將標記了注解的類罚舱、方法等作為輸入內(nèi)容俊戳,經(jīng)過注解處理器進行處理,產(chǎn)生需要的java代碼馆匿。
Android Gradle插件2.2版本發(fā)布后,Android 官方提供了annotationProcessor來代替android-apt燥滑,annotationProcessor支持 javac 和 jack 編譯方式渐北,而android-apt只支持 javac 編譯方式。
使用
- 直接在Module中使用铭拧,比之前Android-apt使用方式更加簡單赃蛛。
dependencies {
compile 'com.github.huweijian5:AwesomeTool:latest_version'
annotationProcessor 'com.github.huweijian5:AwesomeTool-compiler:latest_version'
}
實例說明
接下來以本人寫的一個注解實現(xiàn)線程切換的項目為例恃锉,講解下編譯時注解的編碼過程。
項目結(jié)構(gòu)
本項目主要分為三個Module呕臂,分別為lib_api破托,lib_annotation,lib_compiler歧蒋。
其中l(wèi)ib_api主要存放供外界使用的接口土砂,是對外開放的
lib_annotation里指定了自定義注解的定義
lib_compiler里實現(xiàn)注解處理器,是本項目的核心
-
項目目錄結(jié)構(gòu)如下圖:
這里寫圖片描述 依賴關(guān)系圖如下:
app->lib_api:dependence
lib_api->lib_annotation: dependence
lib_compiler->lib_annnotaion: dependence
值得注意的是谜洽,lib_annotation和lib_compiler都是java工程(apply plugin: 'java')萝映,而lib_api是android工程(apply plugin: 'com.android.library')
lib_annotation
此Module主要實現(xiàn)自定義的注解定義
/**
* 注入對象實例
* Created by HWJ on 2017/3/12.
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface InjectObject {
/**
* 線程優(yōu)先級-20~19,-20代表優(yōu)先級最高,詳見android.os.Process,默認為THREAD_PRIORITY_DEFAULT(0)
* @return
*/
int priority() default 0;
}
/**
* 后臺線程注解
* Created by HWJ on 2017/3/12.
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface WorkInBackground {
}
/**
1. UI線程注解
2. Created by HWJ on 2017/3/12.
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface WorkInMainThread {
}
lib_api
- 定義接口
public interface IObjectInjector<T> {
void inject(T t);
}
- 利用反射進行注入實例
public static void inject(Object target) {
//獲取生成類全稱
Class<?> clazz = target.getClass();
String proxyClassFullName = clazz.getName() + ConstantValue.SUFFIX;
Class<?> proxyClazz = null;
try {
//反射生成類實例對象并進行注入
proxyClazz = Class.forName(proxyClassFullName);
IObjectInjector objectInjector = (IObjectInjector) proxyClazz.newInstance();
objectInjector.inject(target);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("注入失敳椤:"+e.getMessage());
}
}
lib_compiler
lib_compiler中實現(xiàn)注解處理器序臂,是項目的核心,本項目是使用Handler和HandlerThread實現(xiàn)線程切換实束,對于HandlerThread線程切換的使用網(wǎng)絡(luò)文章已經(jīng)有很多奥秆,本文不再贅述。
1 添加以下依賴:
dependencies {
...
//auto-service庫可以幫我們?nèi)ド蒑ETA-INF等信息
compile 'com.google.auto.service:auto-service:1.0-rc4'
//用于生成源代碼
compile 'com.squareup:javapoet:1.9.0'
}
//只有android N支持java8咸灿,如果你寫1.8之后构订,強制要你使用buildToolsVersion為24.0.0
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
...
注意此Module為java工程,而不是android工程析显,如果弄錯了就會報找不到類AbstractProcessor的錯誤
2 繼承AbstractProcessor,并實現(xiàn)方法init,getSupportedAnnotationTypes,getSupportedSourceVersion,process四個方法即可鲫咽,參考代碼如下:
@AutoService(Processor.class)
public class AwesomeToolProcessor extends AbstractProcessor {
private static final String TAG = "AwesomeToolProcessor";
private Filer mFileUtils;//跟文件相關(guān)的輔助類,生成JavaSourceCode
private Elements elementUtils;//跟元素相關(guān)的輔助類谷异,幫助我們?nèi)カ@取一些元素相關(guān)的信息
private Messager messager;//跟日志相關(guān)的輔助類
private Map<String, AwesomeToolProxyInfo> proxyInfoMap = new HashMap<String, AwesomeToolProxyInfo>();//key為注解所在類的全名
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
elementUtils = processingEnv.getElementUtils();
mFileUtils=processingEnv.getFiler();
}
/**
* 支持的注解類型
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(InjectObject.class.getCanonicalName());
supportTypes.add(WorkInBackground.class.getCanonicalName());
supportTypes.add(WorkInMainThread.class.getCanonicalName());
return supportTypes;
}
/**
* 注解處理器支持到的JAVA版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
printMessage("SupportedSourceVersion=%s",SourceVersion.latestSupported().name());
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
printMessage("process:annotations size=%d", annotations.size());
proxyInfoMap.clear();
handleInjectObjectAnnotation(roundEnv);
handleWorkInBackgroundAnnotation(roundEnv);
handleWorkInMainThreadAnnotation(roundEnv);
printMessage("AwesomeToolProxyInfo Map size=%d", proxyInfoMap.size());
generateSourceFiles();
return false;//如果返回true,當有兩個注解作用在同一方法上分尸,那么第一個處理完了之后就不會再處理第二個
}
注解@AutoService(Processor.class)可以自動幫我們處理一些工作,簡化代碼
init中可以獲得Messager用來打印信息歹嘹,打印的信息會顯示在AndroidStudio的Gradle Console窗口
同時也可以獲得Elements箩绍,用來獲取元素的相關(guān)信息,還有Filer,可以用來生成代碼getSupportedAnnotationTypes里需要返回支持的注解類型尺上,就是lib_annotation中定義的注解
getSupportedSourceVersion為注解處理器支持到的java版本
process里處理注解元素作用的類方法等材蛛,根據(jù)自己的業(yè)務邏輯處理并生成相應代碼
3 處理注解
通過RoundEnvironment.getElementsAnnotatedWith()可以獲得注解所在的方法類等,如下
Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(WorkInMainThread.class);
- 其中Element的類型及說明如下:
類型 | 說明 |
---|---|
ExecutableElement | Represents a method, constructor, or initializer (static or instance) of a class or interface, including annotation type elements. |
VariableElement | Represents a field, {@code enum} constant, method or constructor parameter, local variable, resource variable, or exception parameter. |
PackageElement | Represents a package program element. Provides access to information about the package and its members. |
- 獲取方法參數(shù),參考代碼如下:
for (VariableElement variableElement : executableElement.getParameters()) {
System.out.println("參數(shù)類型及名稱:" + variableElement.asType() + "," + variableElement.getSimpleName());
}
4 生成代碼
生成代碼的方式可以通過手動拼接字符串怎抛,也可以通過開源庫javapoet實現(xiàn)卑吭。
try {
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(
proxyInfo.getProxyClassFullName(),//類名全稱
proxyInfo.getTypeElement());//類元素
Writer writer = jfo.openWriter();
writer.write(proxyInfo.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e) {
error(proxyInfo.getTypeElement(),
"Unable to write injector for type %s: %s",
proxyInfo.getTypeElement(), e.getMessage());
}
至此已經(jīng)走完了編譯時注解的整個流程,最后貼下生成的代碼:
//Generated code. Do not modify!
//自動生成代碼马绝,請勿修改豆赏!
package com.junmeng.aad;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import com.junmeng.api.inter.IObjectInjector;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class BlankFragmentHelper implements IObjectInjector<BlankFragment> {
public static final int MESSAGE_NEEDWORKINTHREAD = 1;
public static final int MESSAGE_NEEDWORKINMAINTHREAD = 2;
private Handler mainHandler;
private Handler workHandler;
private HandlerThread handlerThread;
private WeakReference<BlankFragment> target;
@Override
public void inject(final BlankFragment target) {
if (target.blankFragmentHelper != null) {
target.blankFragmentHelper.quit();
}
target.blankFragmentHelper = new BlankFragmentHelper();
target.blankFragmentHelper.init(target);
}
public void init(final BlankFragment target) {
this.target = new WeakReference<BlankFragment>(target);
handlerThread = new HandlerThread("thread_BlankFragmentHelper", -16);
handlerThread.start();
mainHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
List<Object> params;
switch (msg.what) {
case MESSAGE_NEEDWORKINMAINTHREAD:
params = (List<Object>) msg.obj;
target.needWorkInMainThread();
break;
}
}
};
workHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
List<Object> params;
switch (msg.what) {
case MESSAGE_NEEDWORKINTHREAD:
params = (List<Object>) msg.obj;
target.needWorkInThread((java.lang.String) params.get(0), (int) params.get(1), (double) params.get(2), (com.junmeng.aad.Test) params.get(3));
break;
}
}
};
}
public void needWorkInThread(java.lang.String str, int i, double d, com.junmeng.aad.Test test) {
List<Object> params = new ArrayList<>();
params.add(str);
params.add(i);
params.add(d);
params.add(test);
workHandler.sendMessage(workHandler.obtainMessage(MESSAGE_NEEDWORKINTHREAD, params));
}
public void needWorkInMainThread() {
List<Object> params = new ArrayList<>();
mainHandler.sendMessage(mainHandler.obtainMessage(MESSAGE_NEEDWORKINMAINTHREAD, params));
}
/**
* 在不用時務必調(diào)用此方法,防止內(nèi)存泄漏
*/
public void quit() {
if (handlerThread != null && handlerThread.isAlive()) {
handlerThread.quitSafely();
}
}
}
另外,說明下google的auto-service實際上會幫助我們生成jar包并添加META-INF信息掷邦,如下圖
這里寫圖片描述
如有錯誤之處請指正白胀,謝謝。
待解決的關(guān)鍵問題
- 按照上面的實現(xiàn)只能在build的時候才能生成代碼抚岗,有沒有即時生成的技術(shù)呢或杠?還沒找到答案。