註解(Annotation)-Android講解

對於註解(Annotation),咱們在項目中常常會使用到,因此咱們有必要知道註解(Annotation)是怎麼工做的,是怎麼幫咱們把複雜的業務邏輯解耦的;怎麼自定義本身的註解(Annotation)html

註解(Annotation)有兩種,一種是運行時註解, 一種是編譯時註解,下面咱們一一介紹java

元註解

咱們先來簡單介紹一下元註解;元註解就是用來定義自定義註解的註解,咱們經常使用的元註解就四個@Target@Documented@Retention@Inheritedandroid

  • @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的對註解的解析有兩種方案,一種是編譯時解析(生成索引類),一種是運行時解析,下面咱們來看看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()方法時作線程切換等

Lifecycle

咱們再來看一個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方法中調用GenericLifecycleObserveronStateChanged方法,最後調用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文件來作相應的業務處理

怎麼定義一個編譯時註解

  1. 建立本身的註解處理器

    因爲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方法的實現

  2. 註冊註解處理器

    定義完你的註解處理器以後(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有提示, 會列出全部可配置的註解處理器,選擇你本身的註解處理器便可

  3. 在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 掃盲工做

API 掃盲

  1. Messager對象(在控制檯上打印日誌)

    對於日誌打印,通常你們直接想到的是直接使用LogSystem.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);
    複製代碼

    經常使用的日誌類型就這三種,大多數狀況下控制檯只會輸出WARNINGERROR類型的日誌,若是要輸出其它類型的日誌,須要在gradlew命令上加上--info 或 --debug參數;

    processingEnvAbstractProcessor的一個成員變量

    知道輸出日誌後,咱們就能夠配合日誌 來調試咱們的代碼了,下面介紹去怎麼解析 FieldMethodClass,分別對應註解(Annotation)FieldMethodClass上的應用

  2. 解析Field - VariableElement

    VariableElement表示一個屬性、enum 常量、方法或構造方法參數、局部變量或異常參數;

    下面對於FieldMethodClass的解析都如下面的代碼爲例

    @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

  3. 解析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)
    複製代碼
  4. 解析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的解析比較長,由於寫的比較全,考慮到了父類、接口、範型等等,對於FeildMethod的解析 若是須要考慮範型,也可使用上面的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;
    }
    複製代碼
  5. 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文件

上面的代碼只是介紹了怎麼去解析FieldMethodClass上的信息,下面咱們就要根據這些信息去生成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查看,

  1. 先定義生成一個類的實現

    // 緩存 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;
    }
    複製代碼
  2. 再定義生成一個方法的實現

    // 緩存 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;
    }
    複製代碼
  3. 而後再給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());
        }
    }
    複製代碼
  4. 生成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;
    }
    複製代碼
  5. 使用新生成的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案例有不少,好比ButterknifeEventBus(也支持編譯生成索引文件)、dagger等等,這裏我就不一一介紹了,請自行閱讀源碼

因爲JavaxAPI對於Android開發,基本上不多用,因此對其可能會不熟悉,而我上面只是簡單的介紹了一下經常使用的api,因此其它的api請自行查看文檔 java API 中文文檔

相關文章
相關標籤/搜索