對於註解(Annotation
),咱們在項目中常常會使用到,因此咱們有必要知道註解(Annotation
)是怎麼工做的,是怎麼幫咱們把複雜的業務邏輯解耦的;怎麼自定義本身的註解(Annotation
)html
註解(Annotation
)有兩種,一種是運行時註解
, 一種是編譯時註解
,下面咱們一一介紹java
咱們先來簡單介紹一下元註解;元註解就是用來定義自定義註解的註解,咱們經常使用的元註解就四個@Target
、@Documented
、@Retention
、@Inherited
android
@Target
: 用來指定註解的使用範圍;好比類、方法、字段等git
public enum ElementType {
TYPE, // 類聲明
FIELD, // 字段聲明
METHOD, //方法聲明
PARAMETER, //參數聲明
CONSTRUCTOR, //構造函數聲明
LOCAL_VARIABLE, // 局部變量聲明
ANNOTATION_TYPE, // 註釋類型聲明
PACKAGE, // 包聲明
TYPE_PARAMETER, // 類型參數聲明(經常使用於泛型的類型參數進行註解)
TYPE_USE; // 類型使用聲明(經常使用於泛型的類型參數進行註解)
private ElementType() {
}
}
複製代碼
@Documented
: 用來指定被標註的註解會包含在javadoc中github
@Retention
: 用來指定註解的生命週期,好比源碼、class、運行時api
public enum RetentionPolicy {
SOURCE, // 源碼
CLASS, // class
RUNTIME; // 運行時
private RetentionPolicy() {
}
}
複製代碼
@Inherited
: 指定子類能夠繼承父類的註解,只能是類上的註解,方法和字段的註解不能被繼承緩存
運行時註解是指程序在運行的過程當中,經過反射去獲取方法、屬性等成員的註解信息,來實現一些業務邏輯bash
好比給屬性自動賦值app
定義BindValue
以下:maven
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindValue {
public String value() default "";
}
複製代碼
而後定義一個解析BindValue
的處理類
public class BindValueProcessor {
public static void bind(Object object) {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
// 經過field.getAnnotation拿到BindValue註解信息
// method、class等都有getAnnotation方法獲取註解信息
BindValue bindValue = field.getAnnotation(BindValue.class);
try {
if (bindValue != null) {
// 給field 賦值 爲 @BindValue 註解上的 value 值
// 若是field是private,則須要調用setAccessible
// field.setAccessible(true);
field.set(object, bindValue.value());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
複製代碼
最後再使用@BindValue
註解
public class AnnotationActivity extends AppCompatActivity {
// 給name賦值爲 張三
@BindValue("張三")
public String name;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annotation);
// 調用此方法解析@BindValue註解
BindValueProcessor.bind(this);
}
}
複製代碼
運行時註解其實比較簡單,就是經過反射獲取對應成員的註解信息,而後作相應處理
上面的demo比較簡單,主要是爲了講解運行時註解的工做原理,而後怎麼去方便的定義一個本身的運行時註解
下面咱們來看看運行時註解 在 Android
中的一些案例
看源碼得知EventBus
的對註解的解析有兩種方案,一種是編譯時解析(生成索引類),一種是運行時解析,下面咱們來看看EventBus
的運行時解析註解的源碼
先看看findSubscriberMethods
的實現,這裏面就有註解解析兩種方案的實現分支
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
...
// ignoreGeneratedIndex 默認爲false
if (ignoreGeneratedIndex) {
// 運行時解析註解(使用反射)
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 使用編譯時解析註解生成的索引(注意 須要手動開啓索引)
// 若是在findUsingInfo沒有找到索引,則仍是使用反射
subscriberMethods = findUsingInfo(subscriberClass);
}
...
}
複製代碼
下面咱們來看看EventBus
反射的具體實現
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
// while循環--讀取父類的@Subscribe註解的信息
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
// 這個方法就是真正使用反射去解析註解的方法了
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
複製代碼
其實上面的代碼 跟 我一開始寫的 demo 原理同樣,只是EventBus
考慮到一些複雜的業務場景,作了一些封裝而已;好比將method
和@Subscribe
註解上的信息封裝成SubscriberMethod
對象並保存起來,最後在調用EventBus.getInstance().post(Event)
時,找到對應的SubscriberMethod
對象,而後根據註解上的threadMode
信息等在調用method.invoke()
方法時作線程切換等
咱們再來看一個Android
裏很是經常使用的組件庫Lifecycle
, 它的@OnLifecycleEvent
也是運行時註解
廢話很少說,直接看源碼
// 解析Class @OnLifecycleEvent註解,並封裝成CallbackInfo對象
CallbackInfo getInfo(Class klass) {
CallbackInfo existing = mCallbackMap.get(klass);
if (existing != null) {
return existing;
}
existing = createInfo(klass, null);
return existing;
}
private CallbackInfo createInfo(Class klass, @Nullable Method[] declaredMethods) {
Class superclass = klass.getSuperclass();
Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>();
if (superclass != null) {
// 使用遞歸--不斷解析父類的@OnLifecycleEvent註解
CallbackInfo superInfo = getInfo(superclass);
if (superInfo != null) {
handlerToEvent.putAll(superInfo.mHandlerToEvent);
}
}
// 遍歷全部的接口,解析@OnLifecycleEvent註解
Class[] interfaces = klass.getInterfaces();
for (Class intrfc : interfaces) {
for (Map.Entry<MethodReference, Lifecycle.Event> entry : getInfo(
intrfc).mHandlerToEvent.entrySet()) {
// 發現重複定義,會覆蓋
verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
}
}
// 真正使用反射--解析@OnLifecycleEvent註解
Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
boolean hasLifecycleMethods = false;
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation == null) {
continue;
}
hasLifecycleMethods = true;
Class<?>[] params = method.getParameterTypes();
int callType = CALL_TYPE_NO_ARG;
if (params.length > 0) {
callType = CALL_TYPE_PROVIDER;
if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
throw new IllegalArgumentException(
"invalid parameter type. Must be one and instanceof LifecycleOwner");
}
}
Lifecycle.Event event = annotation.value();
if (params.length > 1) {
callType = CALL_TYPE_PROVIDER_WITH_EVENT;
if (!params[1].isAssignableFrom(Lifecycle.Event.class)) {
throw new IllegalArgumentException(
"invalid parameter type. second arg must be an event");
}
if (event != Lifecycle.Event.ON_ANY) {
throw new IllegalArgumentException(
"Second arg is supported only for ON_ANY value");
}
}
if (params.length > 2) {
throw new IllegalArgumentException("cannot have more than 2 params");
}
MethodReference methodReference = new MethodReference(callType, method);
// 發現重複定義,會覆蓋
verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
}
CallbackInfo info = new CallbackInfo(handlerToEvent);
mCallbackMap.put(klass, info);
mHasLifecycleMethods.put(klass, hasLifecycleMethods);
return info;
}
複製代碼
Lifecycle
其實就是將class
的@OnLifecycleEvent
註解信息封裝一個CallbackInfo
對象,而後再封裝成一個GenericLifecycleObserver
對象, 並緩存在Lifecycling
的緩存中,而後在當生命週期發生變化的時候在dispatchEvent
方法中調用GenericLifecycleObserver
的onStateChanged
方法,最後調用mMethod.invoke(target)
方法實現回調
static class ObserverWithState {
State mState;
GenericLifecycleObserver mLifecycleObserver;
ObserverWithState(LifecycleObserver observer, State initialState) {
// 若是緩存裏有 則直接從緩存中取,不然開始解析observer的@OnLifecycleEvent註解信息,並緩存起來
mLifecycleObserver = Lifecycling.getCallback(observer);
mState = initialState;
}
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = getStateAfter(event);
mState = min(mState, newState);
// 若是是反射的實現,則mLifecycleObserver 是ReflectiveGenericLifecycleObserver對象
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
}
class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
private final Object mWrapped;
private final CallbackInfo mInfo;
ReflectiveGenericLifecycleObserver(Object wrapped) {
mWrapped = wrapped;
mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass());
}
@Override
public void onStateChanged(LifecycleOwner source, Event event) {
// 在invokeCallbacks方法中會調用 mMethod.invoke(target) 方法實現回調
mInfo.invokeCallbacks(source, event, mWrapped);
}
}
複製代碼
可見實現一個運行時註解其實很是簡單,直接使用反射獲取註解信息,而後作相應的邏輯封裝處理便可
運行時註解有一個很大的問題就是性能問題,由於使用了java的反射機制;因此這就須要你本身去作衡量了,去作各類優化處理了
下面咱們來看看編譯時註解
編譯時註解是指註解處理器(Annotation Processor)
在代碼編譯的過程當中掃描和處理代碼中的註解(Annotation)
; 正所謂是在代碼編譯的過程當中處理,因此通常對代碼運行性能沒什麼影響;
因爲編譯時註解不能對已有的java類作任何修改,因此通常都是用來生成新的java文件來作相應的業務處理
建立本身的註解處理器
因爲Android
項目是沒辦法使用javax
包下的AbstractProcessor
類,因此必需要使用AndroidStudio新建一個java library
(File -> new Module -> 選擇Java Library
), 而後定義一個AbstractProcessor
的子類, 並實現對應的方法
public class BindValueProcessor extends AbstractProcessor {
/**
* 初始化
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
/**
* 支持的java版本號
* 推薦使用SourceVersion.latestSupported()
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 定義這個註解處理器處理哪些註解(必須重寫,不然不會處理任何註解,即不會走process回調)
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindValue.class.getCanonicalName());
return types;
}
/**
* 基本上不須要重寫這個方法
* 主要是定義在build.gradle文件下配置的arguments支持的參數
* 定義以後能夠在process方法中使用processingEnv.getOptions().get(key)獲取value;
* javaCompileOptions {
* annotationProcessorOptions {
* arguments = [ key : 'value' ]
* }
* }
*/
@Override
public Set<String> getSupportedOptions() {
return super.getSupportedOptions();
}
/**
* 核心方法--在這個方法裏解析註解信息,並生成新的java文件等
* @param annotations
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
...
return true;
}
}
複製代碼
對於AbstractProcessor
的各個方法的解釋請看對應方法上的註釋,其中很是重要的方法是對於process
方法的實現,下面會介紹process
方法的實現
註冊註解處理器
定義完你的註解處理器以後(BindValueProcessor)
以後,而後就須要註冊你的註解處理器
在main
下新建resources
文件夾,而後在resources
下新建META-INF
文件夾,而後在META-INF
下新建services
文件夾,最後在services
下新建javax.annotation.processing.Processor
文件;即文件結構爲main/resources/META-INF/services/javax.annotation.processing.Processor
最後打開javax.annotation.processing.Processor
文件將你的註解處理器添加到裏面(包名+類名); 好比我demo中的com.fc.annotation.annotation.BindValueProcessor
; 當編輯javax.annotation.processing.Processor
文件的時候,其實AndroidStudio
有提示, 會列出全部可配置的註解處理器,選擇你本身的註解處理器便可
在app中使用你的註解處理器
直接在build.gradle添加依賴便可
dependencies {
...
// 針對Java
annotationProcessor project(":library")
// 針對Kotlin
kapt project(":library")
}
複製代碼
這裏是直接依賴的你的上面新建的Java Library
module, 你能夠將這個Library
發版到maven
倉庫,而後再去依賴maven
倉庫裏的library, 這不是我要說的重點,因此對於怎麼發版到maven
倉庫 請本身研究
到這裏的話,整個流程都作的差很少了,如今就差process
方法的具體實現了,這也是對於一個新手比較迷茫的地方,由於對於javax.annotation.processing
包下的各類Api
不熟,不知道怎麼用;下面咱們先來作一下API 掃盲
工做
Messager對象(在控制檯上打印日誌)
對於日誌打印,通常你們直接想到的是直接使用Log
或System.out.println()
, 當你用這些方法輸出日誌的時候,你會發現控制檯沒有任何你的日誌;對於註解處理器,若是想要輸出日誌,就須要使用它提供的Messager
對象
// 輸出警告類型的日誌
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message);
// 輸出錯誤類型的日誌,會致使build失敗
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
// 輸出記錄類型的日誌,大多數狀況下不會控制檯不會輸出這種類型的日誌
// 若是要輸出這種類型的日誌,則須要加上 --info 或 --debug(好比 ./gradlew assembleDebug --info)
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
複製代碼
經常使用的日誌類型就這三種,大多數狀況下控制檯只會輸出WARNING
和ERROR
類型的日誌,若是要輸出其它類型的日誌,須要在gradlew命令上加上--info 或 --debug
參數;
processingEnv
是AbstractProcessor
的一個成員變量
知道輸出日誌後,咱們就能夠配合日誌 來調試咱們的代碼了,下面介紹去怎麼解析 Field
、Method
、Class
,分別對應註解(Annotation)
在Field
、Method
、Class
上的應用
解析Field
- VariableElement
VariableElement
表示一個屬性、enum 常量、方法或構造方法參數、局部變量或異常參數;
下面對於Field
、Method
、Class
的解析都如下面的代碼爲例
@BindValue
public class AnnotationActivity<T, K extends List> extends AppCompatActivity implements Runnable {
@BindValue
public String name;
@BindValue
public void test(View view, String a, List<String> list) {
}
...
}
複製代碼
首先咱們在process方法中能夠獲取被@BindValue
註解的所有Element
元素集合
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
// 獲取 被@BindValue註解的Element元素集合,下面主要介紹VariableElement、ExecutableElement、TypeElement
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindValue.class);
...
}
複製代碼
好比咱們要解析被@BindValue
註解的name
屬性
/**
* 解析被註解的field
* @param element
*/
private void resolveVariableElement(VariableElement element) {
StringBuffer buffer = new StringBuffer();
// 獲取修飾符 public、static、final等等
for (Modifier modifier : element.getModifiers()) {
buffer.append(modifier.toString()).append(" ");
}
// 獲取field 類型
buffer.append(element.asType().toString()).append(" ");
// 獲取field 名稱
buffer.append(element.getSimpleName());
processingEnv.getMessager().printMessage("====VariableElement: "+ buffer.toString());
}
複製代碼
輸出的日誌以下:
====VariableElement: public java.lang.String name
複製代碼
因爲VariableElement
也能夠表示方法的參數,因此 對於方法參數的解析可使用上面的api
解析Method
- ExecutableElement
ExecutableElement
表示某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註釋類型元素
好比咱們要解析被@BindValue
註解的test
方法
/**
* 解析被註解的method
* @param element
*/
private void resolveExecutableElement(ExecutableElement element) {
StringBuffer buffer = new StringBuffer();
// 獲取修飾符 public、static、final等等
for (Modifier modifier : element.getModifiers()) {
buffer.append(modifier.toString()).append(" ");
}
// 獲取返回 類型
buffer.append(element.getReturnType().toString()).append(" ");
// 獲取方法 名稱
buffer.append(element.getSimpleName());
// 獲取方法 參數
buffer.append("(");
List<? extends VariableElement> parameters = element.getParameters();
for (VariableElement parameterElement : parameters) {
// 獲取方法 參數類型和參數名
buffer.append(parameterElement.asType().toString() + " " + parameterElement.getSimpleName() + ", ");
}
buffer.append(")");
println("====ExecutableElement: "+ buffer.toString());
}
複製代碼
輸出的日誌以下:
====ExecutableElement: public void test(android.view.View view, java.lang.String a, java.util.List<java.lang.String> list)
複製代碼
解析Class
- TypeElement
TypeElement
表示一個類或接口程序元素
好比咱們要解析被@BindValue
註解的AnnotationActivity
類的信息
/**
* 解析被註解的class、interface、enum
* @param element
*/
private void resolveTypeElement(TypeElement element) {
StringBuffer buffer = new StringBuffer();
// 獲取修飾符 public、static、final等等
for (Modifier modifier : element.getModifiers()) {
buffer.append(modifier.toString()).append(" ");
}
// 獲取被註解的class 是類、接口、仍是枚舉
if (element.getKind() == ElementKind.CLASS) { // class
buffer.append(element.getKind().name().toLowerCase()).append(" ");
} else if (element.getKind() == ElementKind.INTERFACE) { // interface
buffer.append(element.getKind().name().toLowerCase()).append(" ");
} else if (element.getKind() == ElementKind.ENUM) { // enum
buffer.append(element.getKind().name().toLowerCase()).append(" ");
}
// 獲取class 名稱
buffer.append(element.getSimpleName());
// 獲取定義的class範型信息(即"<>"指定的信息)
List<? extends TypeParameterElement> patterns = element.getTypeParameters();
if (patterns != null && !patterns.isEmpty()) {
buffer.append("<");
for (TypeParameterElement typeParameterElement : patterns) {
DeclaredType typeMirror = (DeclaredType) typeParameterElement.getBounds().get(0);
if (typeMirror.toString().equals("java.lang.Object")) { // 若是沒有指定範型的類型,則是Object
buffer.append(typeParameterElement.getSimpleName());
} else {
buffer.append(typeParameterElement.getSimpleName() + " extends " + typeMirror.asElement().getSimpleName());
}
buffer.append(", ");
}
buffer.delete(buffer.length() - 2, buffer.length());
buffer.append(">");
}
buffer.append(" ");
// 獲取繼承的父類信息
TypeMirror superMirror = element.getSuperclass();
if (superMirror instanceof DeclaredType) {
// 父類也是TypeElement類型,因此直接強轉
TypeElement superElement = (TypeElement) processingEnv.getTypeUtils().asElement(superMirror);
// 又能夠 使用resolveTypeElementSet解析父類
// resolveTypeElementSet(superElement);
buffer.append("extends " + superElement.getSimpleName()).append(" ");
// 解析父類的範型信息 如上操做便可
}
// 獲取實現的接口
List<? extends TypeMirror> interfaceTypeMirror = element.getInterfaces();
if (interfaceTypeMirror != null && !interfaceTypeMirror.isEmpty()) {
buffer.append("implements").append(" ");
for (TypeMirror typeMirror : interfaceTypeMirror) {
// 接口也是TypeElement類型,因此直接強轉
TypeElement interfaceElement = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
// 又能夠 使用resolveTypeElementSet解析interface
// resolveTypeElementSet(interfaceElement);
buffer.append(interfaceElement.getSimpleName()).append(", ");
}
// 刪除最後的", "
buffer.delete(buffer.length() - 2, buffer.length());
}
println("====TypeElement: "+ buffer.toString());
}
複製代碼
輸出日誌以下:
====TypeElement: public class AnnotationActivity<T, K extends List> extends AppCompatActivity implements Runnable
複製代碼
上面的代碼對於class的解析比較長,由於寫的比較全,考慮到了父類、接口、範型等等,對於Feild
和Method
的解析 若是須要考慮範型,也可使用上面的getTypeParameters()
去處理
最後再看看process
如今的實現
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
if (element instanceof ExecutableElement) {
resolveExecutableElement((ExecutableElement) element);
} else if (element instanceof VariableElement) {
resolveVariableElement((VariableElement) element);
} else if (element instanceof TypeElement) {
resolveTypeElement((TypeElement) element);
} else {
}
}
}
return true;
}
複製代碼
Filer對象(生成java文件)
Filer
對象的API
比較少,用起來也比較簡單,經常使用來生成文件, 好比
try {
Filer filer = processingEnv.getFiler();
JavaFileObject sourceFile = filer.createSourceFile("Test");
BufferedWriter writer = new BufferedWriter(sourceFile.openWriter());
writer.write("public class Test {\n");
writer.write(" public static void main(String[] args) {\n");
writer.write(" System.out.println(\"test\");\n");
writer.write(" }\n");
writer.write("}\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
複製代碼
在實際的應用,通常結合第三方的javapoet工具生成java文件
上面的代碼只是介紹了怎麼去解析Field
、Method
、Class
上的信息,下面咱們就要根據這些信息去生成Java
文件了
Java
文件下面就以一個簡單的Demo爲例;好比 對於 下面的java文件
public class AnnotationActivity extends AppCompatActivity {
@BindValue("張三")
public String name;
@BindValue("李四")
public String name2;
...
}
複製代碼
須要生成以下java文件:
public final class AnnotationActivity_Binding {
public final void bind(AnnotationActivity target) {
target.name = "張三";
target.name2 = "李四";
}
}
複製代碼
這裏是使用javapoet工具生成java文件,對於javapoet
的api文檔(文檔寫的比較全),請自行去github查看,
先定義生成一個類的實現
// 緩存 TypeSpec.Builder
private Map<Name, TypeSpec.Builder> typeSpecCache = new HashMap<>();
// 定義一個生成class的 TypeSpec.Builder
private TypeSpec.Builder createOrGetTypeSpecBuilder(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
TypeSpec.Builder typeSpecBuilder = typeSpecCache.get(name);
if (typeSpecBuilder == null) {
// 定義新的class的類名(原類名+"_Binding")
// (即: public final class AnnotationActivity_Binding)
typeSpecBuilder = TypeSpec.classBuilder(typeElement.getSimpleName() + "_Binding")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
typeSpecCache.put(name, typeSpecBuilder);
}
return typeSpecBuilder;
}
複製代碼
再定義生成一個方法的實現
// 緩存 MethodSpec.Builder
private Map<Name, MethodSpec.Builder> bindMethodCache = new HashMap<>();
// 定義一個生成bind方法的 MethodSpec.Builder
private MethodSpec.Builder createOrGetBindMethodSpecBuilder(VariableElement element) {
Name name = ((TypeElement) element.getEnclosingElement()).getQualifiedName();
MethodSpec.Builder builder = bindMethodCache.get(name);
if (builder == null) {
// 經過 element.getEnclosingElement() 能夠獲取到 name 屬性所在的 class信息(即AnnotationActivity)
TypeName typeName = TypeName.get(element.getEnclosingElement().asType());
if (typeName instanceof ParameterizedTypeName) { // 處理範型(若是AnnotationActivity支持範型,則必定要處理)
typeName = ((ParameterizedTypeName) typeName).rawType;
}
// 定義bind方法(即: public final void bind(AnnotationActivity target))
builder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addParameter(typeName, "target");
bindMethodCache.put(name, builder);
}
return builder;
}
複製代碼
而後再給bind方法添加參數和方法體的具體實現
private void createAndImplBindMethod(VariableElement element) {
// 檢查是否是private屬性
boolean privateFlag = false;
for (Modifier modifier : element.getModifiers()) {
if (modifier == Modifier.PRIVATE) {
privateFlag = true;
break;
}
}
BindValue bindValue = element.getAnnotation(BindValue.class);
MethodSpec.Builder methodSpecBuilder = createOrGetBindMethodSpecBuilder(element);
if (privateFlag) { // 若是是private,就使用set方法賦值
String name = element.getSimpleName().toString();
String fieldSetterName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
methodSpecBuilder.addStatement("target.$N($S)", fieldSetterName, bindValue.value());
} else { // 不然直接使用屬性賦值
methodSpecBuilder.addStatement("target.$N = $S", element.getSimpleName(), bindValue.value());
}
}
複製代碼
生成java文件
private void createJavaFile(Set<? extends Element> elements) {
for (Element element : elements) {
if (element instanceof VariableElement) {
createOrGetTypeSpecBuilder((TypeElement) element.getEnclosingElement());
createAndImplBindMethod((VariableElement) element);
}
}
createFile();
}
private void createFile() {
for (Name name : typeSpecCache.keySet()) {
TypeSpec.Builder typeSpecBuilder = typeSpecCache.get(name);
MethodSpec.Builder methodBuilder = bindMethodCache.get(name);
if (methodBuilder != null) {
typeSpecBuilder.addMethod(methodBuilder.build());
}
// 生成的java文件,與被註解的java文件在同一個package下
String packageName = name.toString().substring(0, name.toString().lastIndexOf("."));
JavaFile javaFile = JavaFile.builder(packageName, typeSpecBuilder.build()).build();
try {
// 使用javapoet工具的api生成java文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
因此最後的process的實現以下:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
// 以前寫測試代碼已沒用,直接註釋掉
// for (Element element : elements) {
// if (element instanceof ExecutableElement) {
// resolveExecutableElement((ExecutableElement) element);
// } else if (element instanceof VariableElement) {
// resolveVariableElement((VariableElement) element);
// } else if (element instanceof TypeElement) {
// resolveTypeElement((TypeElement) element);
// } else {
// println("====BindValueProcessor other====");
// }
// }
createJavaFile(elements);
}
return true;
}
複製代碼
使用新生成的Java文件
public class BindValueManager {
public static void bind(Object target) {
String className = target.getClass().getName() + "_Binding";
try {
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
// 建立AnnotationActivity_Binding對象
Object object = constructor.newInstance();
Method method = clazz.getDeclaredMethod("bind", target.getClass());
// 調用AnnotationActivity_Binding對象的bind方法
method.invoke(object, target);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@BindValue
public class AnnotationActivity extends AppCompatActivity {
@BindValue("張三")
public String name;
@BindValue("李四")
public String name2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annotation);
// 重要
BindValueManager.bind(this);
}
...
}
複製代碼
到這裏,編譯時註解運行的基本原理和使用方法介紹的差很少了, 至於編譯時註解的android案例有不少,好比Butterknife
、EventBus
(也支持編譯生成索引文件)、dagger
等等,這裏我就不一一介紹了,請自行閱讀源碼
因爲Javax
的API
對於Android
開發,基本上不多用,因此對其可能會不熟悉,而我上面只是簡單的介紹了一下經常使用的api,因此其它的api請自行查看文檔 java API 中文文檔