1. 程式人生 > >EventBus3例項原始碼淺析(下)-索引類生成

EventBus3例項原始碼淺析(下)-索引類生成

概述

EventBus一般使用在呼叫register註冊時,會通過反射去解析記錄訂閱方法,執行期反射比較耗費效能。3.0提供了高階用法即索引,通過註解處理器在編譯期就提前解析記錄訂閱方法。EventBus在索引生成過程中有使用到TypeElementJavaFileObject等介面,需要先對這些介面有一定了解。

例項解析

使用索引詳細的配置方法可以按照官方文件http://greenrobot.org/eventbus/documentation/subscriber-index/。

這裡繼續用上篇的例子:

public class MyEventSubscriber {

    private WeakReference<
Activity>
mActivity; public EventSubscriber(Activity activity) { this.mActivity = new WeakReference<>(activity); } public void register() { EventBus.getDefault().register(this); } public void unregister() { EventBus.getDefault().unregister(this
); } @Subscribe(threadMode = ThreadMode.MAIN) public void onMyEvent(MyEvent event) { //todo 處理事件 ··· } }

在build.gradle中新增配置:

javaCompileOptions {
    annotationProcessorOptions {
        arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
    }
}

當編譯完成後,會在build->generated->source->apt->debug/release下生成對應的索引類。本例中生成com.example.myapp包,包下有一個MyEventBusIndex類。

原始碼解析

一.繼承自AbstractProcessor

1.先來看該類註冊的兩個註解:

@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")
@SupportedOptions(value = {"eventBusIndex", "verbose"})
public class EventBusAnnotationProcessor extends AbstractProcessor {
	···
}
  • SupportedAnnotationTypes用於註冊該註解處理器支援的註解,這裡僅處理EventBus定義的Subscribe註解;
  • SupportedOptions用於註冊支援的編譯選項,即在本例中在build.gradle中配置的arguments = [ eventBusIndex : ‘com.example.myapp.MyEventBusIndex’ ],eventBusIndex的值對應’com.example.myapp.MyEventBusIndex’ ,verbose預設false。

2.重寫部分方法:

@Override
public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latest();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
	···
}
  • getSupportedSourceVersion返回支援的Java版本,這裡返回最新;
  • process核心方法,編譯過程中會回撥該方法,在這裡實現相應的方法。如果我們成功處理,返回true。否則返回false,會有後續的Processor去處理。

ps:根據觀察日誌發現,註解處理器會執行多輪,它還會處理生成的原始檔中的註解或上一個Processor未處理的註解。

二.重要成員變數

先介紹下該類中的一些重要的成員變數的作用:

public class EventBusAnnotationProcessor extends AbstractProcessor {
	/** 記錄訂閱方法(即註冊了@Subscribe的method),訂閱者類(即method所在的類)為key。 */
	private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
    /** 記錄不處理的訂閱者類。 */
    private final Set<TypeElement> classesToSkip = new HashSet<>();
	/** 標記索引類是否生成完成。 */
    private boolean writerRoundDone;
    /** 標記第幾輪執行process */
    private int round;
}

ps:ListMap是greenrobot對Map擴充套件的集合,對HashMap<K, List<V>>的封裝。

三.核心方法process

直接看程式碼:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
	// annotations為註冊的註解的型別元素的集合,在本例中該集合僅有Subscribe。env用於獲取註解處理環境資訊
	// Messager用於列印日誌
    Messager messager = processingEnv.getMessager();
    try {
    	// 獲取在build.gradle中配置的eventBusIndex引數的值,本例即為com.example.myapp.MyEventBusIndex
        String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
        if (index == null) {
            messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX +
                    " passed to annotation processor");
            return false;
        }
        // 獲取verbose引數值
        verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
        int lastPeriod = index.lastIndexOf('.');
        // 擷取生成索引類的目標包名,即com.example.myapp
        String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;

		// 標記輪次增加
        round++;
        // verbose僅用於判斷是否列印這段日誌
        if (verbose) {
            messager.printMessage(Diagnostic.Kind.NOTE, "Processing round " + round + ", new annotations: " +
                    !annotations.isEmpty() + ", processingOver: " + env.processingOver());
        }
        // 判斷註解處理最後一輪是否結束
        if (env.processingOver()) {
        	// 判斷是否有該處理器支援的註解或程式碼中是否有使用註解
            if (!annotations.isEmpty()) {
            	// 若註解處理過程已經結束,但是仍然有註解集合傳入,則列印錯誤日誌
                messager.printMessage(Diagnostic.Kind.ERROR,
                        "Unexpected processing state: annotations still available after processing over");
                return false;
            }
        }
        // 再對註解集合做一次非空檢查
        if (annotations.isEmpty()) {
            return false;
        }

		// 若已經生成完成,也列印錯誤日誌
        if (writerRoundDone) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "Unexpected processing state: annotations still available after writing.");
        }
        // 以上是檢查完畢,下面開始進行解析記錄
        // 記錄合法的訂閱方法,儲存進methodsByClass集合---①
        collectSubscribers(annotations, env, messager);
        // 記錄不處理的訂閱方法---②
        checkForSubscribersToSkip(messager, indexPackage);

        if (!methodsByClass.isEmpty()) {
        	// 若存在合法的訂閱方法,則開始生成索引類---③
            createInfoIndexFile(index);
        } else {
            messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
        }
        // 完成後標記writerRoundDone為true
        writerRoundDone = true;
    } catch (RuntimeException e) {
        // IntelliJ does not handle exceptions nicely, so log and print a message
        e.printStackTrace();
        messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
    }
    // 最終返回true,表示已經正確處理
    return true;
}

process方法中先進行初步的合法性檢查,之後分三步開始處理:1.解析出所有訂閱方法;2.記錄不處理的訂閱方法;3.生成索引類。接下來逐步分析。

1.初步收集訂閱方法

關鍵在collectSubscribers方法中:

private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
	// 遍歷該處理器註冊的註解元素的集合
    for (TypeElement annotation : annotations) {
    	// 獲取所有使用了該註解的元素集合,在這裡即所有添加了@Subscribe的方法的元素
        Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
        for (Element element : elements) {
            if (element instanceof ExecutableElement) {
            	// ExecutableElement表示可執行方法的元素,因為@Subscribe只能用在method上
                ExecutableElement method = (ExecutableElement) element;
                // 合法性校驗
                if (checkHasNoErrors(method, messager)) {
                	// 獲取ExecutableElement所在類的元素,即訂閱方法所在的訂閱者類
                    TypeElement classElement = (TypeElement) method.getEnclosingElement();
                    // 根據class作key、method作value快取到集合中
                    methodsByClass.putElement(classElement, method);
                }
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
            }
        }
    }
}

上面方法很簡單,就是取得所有新增@Subscribe的method,將符合要求的method根據它所在的class歸類快取到methodsByClass中。

接著看下checkHasNoErrors方法,這裡面如何對method進行校驗:

private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
	// 判斷方法修飾符,不能是靜態方法
    if (element.getModifiers().contains(Modifier.STATIC)) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must not be static", element);
        return false;
    }

	// 方法必須是public的
    if (!element.getModifiers().contains(Modifier.PUBLIC)) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public", element);
        return false;
    }

	// 獲取該方法的引數集合
    List<? extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
    // 引數個數必須是一個
    if (parameters.size() != 1) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must have exactly 1 parameter", element);
        return false;
    }
    return true;
}

可以看出,我們建立的訂閱方法必須符合public、非static、僅有一個引數,否則即使新增@Subscribe也是無效的。

2.篩選訂閱方法

進入checkForSubscribersToSkip方法:

private void checkForSubscribersToSkip(Messager messager, String myPackage) {
	// 遍歷集合key,依次檢查訂閱者類
    for (TypeElement skipCandidate : methodsByClass.keySet()) {
        TypeElement subscriberClass = skipCandidate;
        while (subscriberClass != null) {
        	// 判斷訂閱者類是否可訪問
            if (!isVisible(myPackage, subscriberClass)) {
            	// 若該訂閱者類對於即將生成的目標索引類不可訪問,則新增進篩除集合,結束while迴圈,檢查下一個訂閱者類
                boolean added = classesToSkip.add(skipCandidate);
                if (added) {
                    String msg;
                    if (subscriberClass.equals(skipCandidate)) {
                        msg = "Falling back to reflection because class is not public";
                    } else {
                        msg = "Falling back to reflection because " + skipCandidate +
                                " has a non-public super class";
                    }
                    messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass);
                }
                break;
            }
            // 從集合中取出該訂閱者類中的訂閱方法
            List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
            if (methods != null) {
                for (ExecutableElement method : methods) {
                    String skipReason = null;
                    // 獲取方法的第一個也是唯一一個引數
                    VariableElement param = method.getParameters().get(0);
                    // 獲取引數物件的型別
                    TypeMirror typeMirror = getParamTypeMirror(param, messager);
                    // 引數必須是一個類的宣告型別和類元素,即如本例定義的MyEvent實體類
                    if (!(typeMirror instanceof DeclaredType) ||
                            !(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
                        skipReason = "event type cannot be processed";
                    }
                    // 引數型別校驗通過,則檢查引數物件類的可訪問性
                    if (skipReason == null) {
                        TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
                        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;
                    }
                }
            }
            // subscriberClass切換到父類,若父類是系統的類,則返回null,結束while迴圈
            subscriberClass = getSuperclass(subscriberClass);
        }
    }
}

該方法中依次遍歷訂閱者類及父類,校驗訂閱者類的可訪問性和訂閱方法引數的型別及訪問性,若有不符合要求的就新增進篩除集合。

接著再詳細看下校驗訪問性的方法isVisible:

private boolean isVisible(String myPackage, TypeElement typeElement) {
    Set<Modifier> modifiers = typeElement.getModifiers();
    boolean visible;
    // 也是通過判斷修飾符
    if (modifiers.contains(Modifier.PUBLIC)) {
        visible = true;
    } else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
        visible = false;
    } else {
    	// 若是預設則判斷訂閱者類所在包名和索引類所在包名是否一致
    	// getPackageElement方法中不斷獲取typeElement的頂層元素直至取到包元素,然後獲取包名
        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();
    // Check for generic type
    if (typeMirror instanceof TypeVariable) {
    	// 獲取該型別變數的上邊界,如果該物件有通過extends繼承其他物件,則upperBound為DeclaredType型別
        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;
}

經過篩選將不需要寫入索引類的訂閱者類存進classesToSkip集合中,後續生成的時候會比較這個集合中的類,判斷是跳過還是處理。

3.生成索引類

如果methodsByClass中不為空,則呼叫createInfoIndexFile方法:

private void createInfoIndexFile(String index) {
    BufferedWriter writer = null;
    try {
    	// 通過註解處理的檔案操作工具類建立原始檔
        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);
        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(