EventBus3.0解析之註解處理器

努力啊Ant發表於2019-04-23

在上一篇EventBus3.0原始碼解析中,在介紹查詢訂閱方法時提到了APT解析,當時一筆帶過,主要是覺得這個特性比較重要,所以單獨拎出來寫一篇來介紹。

原始碼

先來回憶下查詢訂閱方法:


    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            // 使用反射查詢
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //  使用生成的index查詢
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }
複製程式碼

findSubscriberMethods方法中,會根據ignoreGeneratedIndex來進行策略執行,這個變數預設是false,呼叫了findUsingInfo方法:


    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        // 準備一個findState例項
        FindState findState = prepareFindState();
        // 初始化
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            // 直接獲取訂閱者資訊
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                // 有訂閱者資訊直接進行下一步檢查
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                // 沒有訂閱者資訊則使用反射一個個類查詢
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

複製程式碼

方法如其名,這個方法主要就是用來查詢本地已經生成的訂閱者資訊,如果沒有查詢到還是要去使用反射獲取。關鍵方法就是getSubscriberInfo方法,它返回了一個SubscriberInfo例項並儲存在了FindState例項中,來看看SubscriberInfo裡有什麼:

public interface SubscriberInfo {
    // 訂閱類
    Class<?> getSubscriberClass();
    // 訂閱方法集合
    SubscriberMethod[] getSubscriberMethods();

    SubscriberInfo getSuperSubscriberInfo();

    boolean shouldCheckSuperclass();
}
複製程式碼

SubscriberInfo是一個介面,通過這個介面可以拿到封裝了訂閱類、訂閱方法以及父類的SubscriberInfo。再回到上一步,看看關鍵方法getSubscriberInfo

    private SubscriberInfo getSubscriberInfo(FindState findState) {
        // 如果之前已經查詢過一次,就直接使用不必重新查詢
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        // 這裡判斷了subscriberInfoIndexes不為空,從subscriberInfoIndexes中去取SubscriberInfo
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }
複製程式碼

一共兩步,第一步是在查詢過一次之後並且找到了目標SubscriberInfo之後才會執行。重點是在第二步,判斷了一個引數subscriberInfoIndexes不為空,然後從subscriberInfoIndexes中遍歷出目標類的SubscriberInfo。那麼subscriberInfoIndexes又是什麼呢:

class SubscriberMethodFinder {
    // 一個SubscriberInfoIndex集合
    private List<SubscriberInfoIndex> subscriberInfoIndexes;
    
    ...
}

// 一個根據訂閱類查詢訂閱資訊的介面
public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}
    
複製程式碼

subscriberInfoIndexes是一個SubscriberInfoIndex集合,它是在EventBus初始化時被賦值的:

public class EventBus {
    EventBus(EventBusBuilder builder) {
    ...
    // 傳入builder.subscriberInfoIndexes
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    
    ...
    }
}
複製程式碼

EventBus初始化則是使用的Builder模式,Builder裡有個addIndex方法:

    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if(subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }
複製程式碼

到了這裡顯而易見,SubscriberInfoIndex是由我們傳參進Builder的。其實SubscriberInfoIndex就是APT預先解析出來的訂閱者資訊提供者,它需要開發者自行在編譯器中配置APT後編譯生成,接下來就來看看如何才能生成並使用索引類SubscriberInfoIndex

Subscriber Index

APT(Annotation Processing Tool)配置

首先要在專案module的build.gradle中引入EventBusAnnotationProcessor依賴, 並設定生成類引數

android {
    defaultConfig {
        javaCompileOptions {
            // 註解處理器引數配置
            annotationProcessorOptions {
                // 配置引數名和值
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
}

dependencies {
    // 註解依賴
    implementation 'org.greenrobot:eventbus:3.0.0'
    // 註解處理器依賴
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.0'
}
複製程式碼

如果使用的是Kotlin語言,就需要使用Kotlin的專用APT:kapt

apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied

dependencies {
    implementation 'org.greenrobot:eventbus:3.0.0'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.0.0'
}

kapt {
    arguments {
        // 包名可以自定義
        arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
    }
}
複製程式碼

如果專案中是JavaKotlin同時使用,可能會存在annotationProcessorkapt同時存在,建議統一改為kapt,因為後者會相容前者。

Index應用

在上一步完成後,只需要Rebuild Project,就會在專案路徑app-build-generated-source-apt/kapt 下看到生成的索引類:MyEventBusIndex.java,它實現了SubscriberInfoIndex介面,實現了getSubscriberInfo方法。從上邊的原始碼分析就知道,接下來只需要將生成的索引類傳參進EventBus中:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
複製程式碼

由原始碼可知,EventBus中儲存的是一個Subscriber Index集合,所以addIndex方法可以呼叫多次,這樣,在非applicationModel中也可以生成各自的Index類,最後統一新增到EventBus中。

來看看生成的索引類MyEventBusIndex.java

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
    
    // 類初始化時直接賦值
    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        // 直接new一個SimpleSubscriberInfo並加入SUBSCRIBER_INDEX
        putIndex(new SimpleSubscriberInfo(com.example.myapp.MainActivity.Companion.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("handleEvent", com.example.myapp.MyEvent.class,
                    ThreadMode.MAIN),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    /**
    * 根據類查詢快取
    **/
    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

複製程式碼

MyEventBusIndex在初始化時,直接會將訂閱者相關資訊快取在Map中,並實現了SubscriberInfoIndex介面,在實現方法getSubscriberInfo中根據訂閱類返回對應的訂閱者資訊。到了這裡,你肯定會疑惑,EventBus是如何在編譯期間就找到了所有訂閱資訊並且是如何生成了一個Java檔案的,奧祕就在APT技術中。

EventBusAnnotationProcessor

在上文中,整個MyEventBusIndex檔案都是由APT在編譯時解析註解生成的。顯然,EventBus自定義了自己的註解處理器。

我們知道,自定義註解處理器只要三步:

  1. 新建自定義註解器(其實就是一個java類),繼承自Java提供的AbstractProcessor
  2. 實現process方法,開發者就是在這個方法中進行註解解析並生成Java類
  3. 註冊到Javac(編譯器)

這樣,在編譯期間,Javac會掃描註解,檢查AbstractProcessor的子類,並且呼叫該子類的process函式,然後將新增了註解的所有元素都傳遞到process函式中,執行我們的處理邏輯,生成我們想要的Java檔案。

那麼重點就在EventBusEventBusAnnotationProcessorprocess方法。在開始之前,需要了解下Element類:

Element

要想自定義APT,必須掌握Element,它是APT技術的基礎。Element只有在編譯期間是可見的,因為它是用來在編譯期間描述Java檔案的靜態結構的一種型別,Element可以表示包、類或方法。JDK提供了5種Element,它們都繼承自Element

- javax.lang.model.element.Element
      - javax.lang.model.element.ExecutableElement
      - javax.lang.model.element.PackageElement
      - javax.lang.model.element.TypeElement 
      - javax.lang.model.element.TypeParameterElement
      - javax.lang.model.element.VariableElement
複製程式碼

它們分別表示:

  • PackageElement:表示一個包程式元素。提供對有關包及其成員的資訊的訪問。
  • TypeElement:表示一個類或介面程式元素。提供對有關型別及其成員的資訊的訪問。注意,列舉型別是一種類,而註釋型別是一種介面。
  • ExecutableElement:表示某個類或介面的方法、構造方法或初始化程式,包括註釋型別元素。
  • TypeParameterElement:表示一般類、介面、方法或構造方法元素的形式型別(泛型)引數。
  • VariableElement:表示一個欄位、enum 常量、方法或構造方法引數、區域性變數或異常引數。

舉個例子:


package com.example;    // PackageElement

public class Demo<T extends List> {    // Demo類是TypeElement,T是TypeParameterElement

    private int a;      // VariableElement
    private String b;  // VariableElement

    public Demo () {}    // ExecuteableElement

    public void setValue (int value) {} // 方法setValue是ExecuteableElement,引數value是VariableElement
}
複製程式碼

再介紹下幾個涉及到的Element方法:

asType:返回此元素定義的型別。返回值用TypeMirror表示,TypeMirror表示了Java程式語言中的型別,包括基本型別,一般用來做型別判斷。

getModifiers:返回此元素的修飾符,不包括註釋。但包括顯式修飾符,比如介面成員的 publicstatic 修飾符。

getEnclosingElement:返回封裝此元素的最裡層元素。

  • 如果此元素的宣告在詞法上直接封裝在另一個元素的宣告中,則返回那個封裝元素。
  • 如果此元素是頂層型別,則返回它的包。
  • 如果此元素是一個包,則返回 null
  • 如果此元素是一個型別引數,則返回 null

process

接下來進入正題,看看EventBus註解處理器的process方法:

public class EventBusAnnotationProcessor extends AbstractProcessor {
    public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex";
    
    /** Found subscriber methods for a class (without superclasses). */
    // 自定義的資料結構,儲存`<Key, List<Value>>`型別資料;這裡的key是訂閱類
    private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
    // 儲存不合法的元素,這裡的key是訂閱類
    private final Set<TypeElement> classesToSkip = new HashSet<>();
    ...
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        // messager用於輸出log
        Messager messager = processingEnv.getMessager();
        try {
            // 獲取到我們在gradle中為apt設定的引數值,即生成類的完整路徑,通過它可以獲得包名和類名
            String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
            if (index == null) {
                return false;
            }
            ...
            
            // 取出所有訂閱者資訊
            collectSubscribers(annotations, env, messager);
            // 檢查訂閱者資訊
            checkForSubscribersToSkip(messager, indexPackage);

            if (!methodsByClass.isEmpty()) {
                // 查詢到的註解的方法不為空,生成Java檔案
                createInfoIndexFile(index);
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
            }
            writerRoundDone = true;
        } catch (RuntimeException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.ERROR, e);
        }
        return true;
    }
    
}
複製程式碼

process方法中做了三件事:

  1. 查詢到所有訂閱者
  2. 檢查出不合法的訂閱者
  3. 根據前兩步的結果生成Java檔案

一個一個方法來看。

collectSubscribers

    private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
        // 遍歷所有註解
        for (TypeElement annotation : annotations) {
            // 拿到被註解標記的所有元素
            Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
            // 遍歷所有元素
            for (Element element : elements) {
                // 元素必須是方法,因為@Subscribe只能註解方法
                if (element instanceof ExecutableElement) {
                    ExecutableElement method = (ExecutableElement) element;
                    // 檢查方法,條件:訂閱方法必須是非靜態的,公開的,引數只能有一個
                    if (checkHasNoErrors(method, messager)) {
                        // 取封裝訂閱方法的類
                        TypeElement classElement = (TypeElement) method.getEnclosingElement();
                        // 以類名為key,儲存訂閱方法
                        methodsByClass.putElement(classElement, method);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
                }
            }
        }
    }
複製程式碼

這裡的流程也是很簡單的,就是遍歷被註解標記的所有元素,篩選出合法的訂閱方法,具體看註釋即可。checkHasNoErrors方法比較簡單,就不貼程式碼了。 這裡還有個陌生的方法:

RoundEnvironmentgetElementsAnnotatedWith方法, 這個方法會返回被當前註解標記的所有元素,可能是類、變數、方法等。

在方法最後,將篩選出來的訂閱者和方法都儲存在了methodsByClass這個容器裡,這個容器的資料結構是<KEY, <List<Value>>>,可以儲存訂閱類中的多個方法。

checkForSubscribersToSkip

查詢不合法的訂閱者

private void checkForSubscribersToSkip(Messager messager, String myPackage) {
        // 遍歷所有訂閱類
        for (TypeElement skipCandidate : methodsByClass.keySet()) {
            TypeElement subscriberClass = skipCandidate;
            //開啟子類到父類的while迴圈檢查
            while (subscriberClass != null) {
                // 訂閱類必須是可訪問的,否則記錄並直接break,檢查下一個訂閱類
                if (!isVisible(myPackage, subscriberClass)) {
                    boolean added = classesToSkip.add(skipCandidate);
                    ...
                    break;
                }
                // 拿到訂閱類的所有訂閱方法
                List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
                if (methods != null) {
                    // 檢查所有訂閱方法
                    for (ExecutableElement method : methods) {
                        String skipReason = null;
                        // 拿到訂閱方法的引數,開始對引數進行檢查
                        VariableElement param = method.getParameters().get(0);
                        // 取得引數(Event)的型別
                        TypeMirror typeMirror = getParamTypeMirror(param, messager);
                        // 引數(Event)的型別必須是類或介面
                        if (!(typeMirror instanceof DeclaredType) ||
                                !(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
                            skipReason = "event type cannot be processed";
                        }
                        if (skipReason == null) {
                            // 拿到引數(Event)元素
                            TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
                            // 檢查引數(Event)元素是不是可訪問的
                            if (!isVisible(myPackage, eventTypeElement)) {
                                skipReason = "event type is not public";
                            }
                        }
                        if (skipReason != null) {
                            // 將不合格的訂閱類記錄下來
                            boolean added = classesToSkip.add(skipCandidate);
                            if (added) {
                                String msg = "Falling back to reflection because " + skipReason;
                                if (!subscriberClass.equals(skipCandidate)) {
                                    msg += " (found in super class for " + skipCandidate + ")";
                                }
                                messager.printMessage(Diagnostic.Kind.NOTE, msg, param);
                            }
                            break;
                        }
                    }
                }
                // 切換到父類,若父類是系統的類,則返回null,結束while迴圈
                subscriberClass = getSuperclass(subscriberClass);
            }
        }
    }
複製程式碼

可以看到這個方法主要是對第一步查詢到的訂閱者和訂閱方法進行檢查,因為要生成的索引類中要訪問訂閱類和事件,所以必須要求訂閱類和Event引數是可訪問的,並且Event引數必須是類或介面型別,因為EventBus的訂閱方法是不支援基本型別的。

來看下可訪問性檢查isVisible

    private boolean isVisible(String myPackage, TypeElement typeElement) {
        // 獲取修飾符
        Set<Modifier> modifiers = typeElement.getModifiers();
        boolean visible;
        if (modifiers.contains(Modifier.PUBLIC)) {
            // public的直接return true
            visible = true;
        } else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
            // private和protected的直接return false
            visible = false;
        } else {
            // 獲取元素所在包名
            String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
            if (myPackage == null) {
                // 是否都在最外層,沒有包名
                visible = subscriberPackage.length() == 0;
            } else {
                // 是否在同一包下
                visible = myPackage.equals(subscriberPackage);
            }
        }
        return visible;
    }
複製程式碼

獲取元素型別方法getParamTypeMirror

    private TypeMirror getParamTypeMirror(VariableElement param, Messager messager) {
        // 獲取元素型別
        TypeMirror typeMirror = param.asType();
        // 元素型別必須得是類或介面型別
        if (typeMirror instanceof TypeVariable) {
            // 獲取該型別變數的上邊界,如果有extends,則返回父類,否則返回Object
            TypeMirror upperBound = ((TypeVariable) typeMirror).getUpperBound();
            // 是宣告型別
            if (upperBound instanceof DeclaredType) {
                if (messager != null) {
                    messager.printMessage(Diagnostic.Kind.NOTE, "Using upper bound type " + upperBound +
                            " for generic parameter", param);
                }
                // 替換引數型別為上邊界的型別
                typeMirror = upperBound;
            }
        }
        return typeMirror;
    }
複製程式碼

createInfoIndexFile

生成Java檔案:

private void createInfoIndexFile(String index) {
        BufferedWriter writer = null;
        try {
            // 通過編譯環境的檔案工具建立Java檔案
            JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
            // 擷取出包名
            int period = index.lastIndexOf('.');
            String myPackage = period > 0 ? index.substring(0, period) : null;
            // 擷取出類名
            String clazz = index.substring(period + 1);
            //開始向Java檔案中寫入程式碼
            writer = new BufferedWriter(sourceFile.openWriter());
            if (myPackage != null) {
                writer.write("package " + myPackage + ";\n\n");
            }
            writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
            writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
            writer.write("import java.util.HashMap;\n");
            writer.write("import java.util.Map;\n\n");
            writer.write("/** This class is generated by EventBus, do not edit. */\n");
            writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
            writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
            writer.write("    static {\n");
            writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
            // 寫入查詢到的訂閱者的相關程式碼
            writeIndexLines(writer, myPackage);
            writer.write("    }\n\n");
            writer.write("    private static void putIndex(SubscriberInfo info) {\n");
            writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
            writer.write("    }\n\n");
            writer.write("    @Override\n");
            writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
            writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
            writer.write("        if (info != null) {\n");
            writer.write("            return info;\n");
            writer.write("        } else {\n");
            writer.write("            return null;\n");
            writer.write("        }\n");
            writer.write("    }\n");
            writer.write("}\n");
        } catch (IOException e) {
            throw new RuntimeException("Could not write source for " + index, e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    //Silent
                }
            }
        }
    }
複製程式碼

驚呆了有沒有,果然是大神,沒有使用第三方框架比如javapoet,硬是一行行寫出來的,佩服!

重點就在writeIndexLines方法了:

private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
        // 遍歷methodsByClass
        for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
            // 跳過不合格的訂閱者
            if (classesToSkip.contains(subscriberTypeElement)) {
                continue;
            }
            // 獲取訂閱類的字串名稱,格式是:包名.類名.class
            String subscriberClass = getClassString(subscriberTypeElement, myPackage);
            // 再一次檢查是否可訪問
            if (isVisible(myPackage, subscriberTypeElement)) {
                writeLine(writer, 2,
                        "putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,",
                        "true,", "new SubscriberMethodInfo[] {");
                // 取出訂閱類的所有訂閱方法
                List<ExecutableElement> methods = methodsByClass.get(subscriberTypeElement);
                // 生成 [new 一個SubscriberMethodInfo,並將訂閱方法的相關資訊寫入]的程式碼
                writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
                writer.write("        }));\n\n");
            } else {
                writer.write("        // Subscriber not visible to index: " + subscriberClass + "\n");
            }
        }
    }
複製程式碼

很簡單,遍歷查詢到的所有訂閱者,然後跳過不合法的訂閱者,生成寫入訂閱者的程式碼。顯然,writeCreateSubscriberMethods方法中生成了寫入訂閱方法的程式碼:

private void writeCreateSubscriberMethods(BufferedWriter writer, List<ExecutableElement> methods,
                                              String callPrefix, String myPackage) throws IOException {
        // 遍歷訂閱類中的所有訂閱方法
        for (ExecutableElement method : methods) {
            // 獲取方法引數
            List<? extends VariableElement> parameters = method.getParameters();
            // 取出第一個引數的引數型別
            TypeMirror paramType = getParamTypeMirror(parameters.get(0), null);
            // 通過引數型別拿到引數元素
            TypeElement paramElement = (TypeElement) processingEnv.getTypeUtils().asElement(paramType);
            // 方法名
            String methodName = method.getSimpleName().toString();
            // 引數型別 類名
            String eventClass = getClassString(paramElement, myPackage) + ".class";

            // 獲取方法上的註解
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
            List<String> parts = new ArrayList<>();
            parts.add(callPrefix + "(\"" + methodName + "\",");
            String lineEnd = "),";
            // 獲取註解的值
            if (subscribe.priority() == 0 && !subscribe.sticky()) {
                if (subscribe.threadMode() == ThreadMode.POSTING) {
                    parts.add(eventClass + lineEnd);
                } else {
                    parts.add(eventClass + ",");
                    parts.add("ThreadMode." + subscribe.threadMode().name() + lineEnd);
                }
            } else {
                parts.add(eventClass + ",");
                parts.add("ThreadMode." + subscribe.threadMode().name() + ",");
                parts.add(subscribe.priority() + ",");
                parts.add(subscribe.sticky() + lineEnd);
            }
            // 生成程式碼
            writeLine(writer, 3, parts.toArray(new String[parts.size()]));

            if (verbose) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Indexed @Subscribe at " +
                        method.getEnclosingElement().getSimpleName() + "." + methodName +
                        "(" + paramElement.getSimpleName() + ")");
            }

        }
    }
複製程式碼

writeLine方法只是按Java格式生成程式碼,無需深入瞭解。最後生成的程式碼就是上面提到的MyEventBusIndex.java

總結

註解處理器的作用其實就是提前在編譯階段就把訂閱者的資訊解析出來,在執行期間直接使用,極大的提高了效率和效能!ButterKnife等知名框架都用到了這種技術,掌握它還是很有必要的!

相關文章