Android 註解系列之EventBus3「加速引擎「(五)

前言

在上篇文章 Android 註解系列之 EventBus3 原理(四)中咱們講解了 EventBus3 的內部原理,在該篇文章中咱們將講解 EventBus3 中的 「加速引擎」---索引類。閱讀該篇文章咱們可以學到以下知識點。html

  • EventBus3 索引類出現的緣由
  • EventBus3 索引類的使用
  • EventBus3 索引類生成的過程
  • EventBus3 混淆注意事項

對 APT 技術不熟悉的小夥伴,能夠查看文章 Android-註解系列之APT工具(三)java

前景回顧

Android 註解系列之 EventBus3 原理(四)中,咱們特別指出在 EventBus3 中優化了 SubscriberMethodFinder 獲取類中包含 @Subscribe 註解的訂閱方法的流程。使其能在 EventBus.register() 方法調用以前就能知道相關訂閱事件的方法,這樣就減小了程序在運行期間使用反射遍歷獲取方法所帶來的時間消耗。優化點以下圖中 紅色虛線框 所示:android

EventBus3優化.jpg

EventBus 做者 Markus Junginger 也給出了使用索引類先後 EventBus 的效率對比,以下圖所示:git

eventbus3-registration-perf-nexus9m.png

從上圖中,咱們可使用索引類後,EventBus 的效率有着明顯的提高,而效率提高的背後,正是使用了 APT 技術所建立的索引類。那麼接下來咱們就來看一看 EventBus3 中是如何結合 APT 技術來進行優化的。github

關鍵代碼

閱讀過 EventBus3 源碼的小夥伴應該都知道,在 EventBus3 中獲取類中包含 @Subscribe 註解的訂閱方法有兩種方式。數組

  • 第一種:是直接在程序運行時反射獲取
  • 第二種:就是經過索引類。

而使用索引類的關鍵代碼爲 SubscriberMethodFinder 中的 getSubscriberInfo() 方法與 findUsingInfo() 方法 。 咱們分別來看這兩個方法。性能優化

findUsingInfo 方法

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            //👇關鍵代碼,從索引類中獲取 SubscriberInfo
            findState.subscriberInfo = getSubscriberInfo(findState);
            //方式1:若是 subscriberInfo 不爲空,則從該對象中獲取 SubscriberMethod 對象
            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 {
                //方式2:若是 subscriberInfo 爲空,那麼直接經過反射獲取
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }
複製代碼

咱們能從該方法中得到如下信息:app

  • EventBus3 中默認會調用 getSubscriberInfo() 方法去獲取 subscriberInfo 對象信息。
  • 若是 subscriberInfo 不爲空,則會從該對象中獲取 SubscriberMethod 數組。
  • 若是 subscriberInfo 爲空,那麼會直接經過反射去獲取 SubscriberMethod 集合信息。

SubscriberMethod 類中含有 @Subscribe 註解的方法信息封裝(優先級,是否粘性,線程模式,訂閱的事件),以及當前方法的 Method 對象(java.lang.reflect 包下的對象)。ide

也就說 EventBus 是否經過反射獲取信息,是由 getSubscriberInfo()方法來決定,那麼咱們查看該方法。函數

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;
            }
        }
        //👇這裏是EventBus3中優化的關鍵,索引類
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }
複製代碼

從代碼邏輯中咱們能得出,若是 subscriberInfoIndexes 集合不爲空的話,那麼就會從 SubscriberInfoIndex(索引類) 中去獲取 SubscriberInfo 對象信息。該方法的邏輯並不複雜,惟一的疑惑就是這個 SubscriberInfoIndex(索引類) 對象是從何而來的呢?

聰明的小夥伴們已經想到了。對!!!就是經過 APT 技術自動生成的類。那麼咱們怎麼使用 EventBus3 中的索引類?以及 EventBus3 中是如何生成的索引類的呢? 不急不急,咱們一個一個的解決問題。咱們先來看看如何使用索引類。

EventBus中索引類的使用

若是須要使用 EventBus3 中的索引類,咱們能夠在 App 的 build.gradle 中添加以下配置:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                // 根據項目實際狀況,指定索引類的名稱和包名
                arguments = [ eventBusIndex : 'com.eventbus.project.EventBusIndex’ ]
            }
        }
    }
}
dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1’
    // 引入註解處理器
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1’
}
複製代碼

若是有小夥伴不熟悉 gradle 配置,能夠查看 AnnotationProcessorOptions

在上述配置中,咱們須要注意以下幾點:

  • 若是你不使用索引類,那麼就沒有必要設置 annotationProcessorOptions 參數中的值。也沒有必要引入 EventBus 的註解處理器。
  • 若是要使用索引類,而且也引入了 EventBus 的註解處理器(eventbus-annotation-processor),但卻沒有設置 arguments 的話,編譯時就會報錯:No option eventBusIndex passed to annotation processor
  • 索引類的生成,須要咱們對代碼從新編譯。編譯成功後,其該類對應路徑爲\ProjectName\app\build\generated\source\apt\你設置的包名

當咱們的索引類生成後,咱們還須要在初始化 EventBus 時應用咱們生成的索引類,代碼以下所示:

EventBus.builder().addIndex(new EventBusIndex()).installDefaultEventBus();
複製代碼

之因此要配置索引類,是由於咱們須要將咱們生成的索引類添加到 subscriberInfoIndexes 集合中,這樣咱們才能從以前講解的 getSubscriberInfo()找到咱們配置的索引類。addIndex() 代碼以下所示:

public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if (subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        //👇這裏添加索引類到 subscriberInfoIndexes 集合中
        subscriberInfoIndexes.add(index);
        return this;
    }
複製代碼

索引類實際使用分析

若是你已經配置好了索引類,那麼咱們看下面的例子,這裏我配置的索引類爲 EventBusIndex 對應包名爲: 'com.eventbus.project' 。我在 EventBusDemo.java 中聲明瞭以下方法:

public class EventBusDemo {

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEventOne(MessageEvent event) {
        System.out.println("hello」);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEventTwo(MessageEvent event) {
        System.out.println("world」);
    }
}

複製代碼

自動生成的索引類,以下所示:

public class EventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(EventBusDemo.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessageEventOne", MessageEvent.class, ThreadMode.MAIN),
            new SubscriberMethodInfo("onMessageEventTwo", MessageEvent.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;
        }
    }
}
複製代碼

在生成的索引類中咱們能夠看出:

  • 生成的索引類中,維護了一個 key 爲 訂閱對象 value 爲 SimpleSubscriberInfo 的 HashMap。
  • SimpleSubscriberInfo 類中維護了當前訂閱者的 class 對象與 SubscriberMethodInfo[] 數組
  • HashMap 中的數據添加是放到靜態代碼塊中執行的。

SubscriberMethodInfo 類中含有 @Subscribe 註解的方法信息封裝(優先級,是否粘性,線程模式,訂閱的事件),以及當前方法的名稱

到如今,咱們已經知道了咱們索引類中的內容,那麼如今在回到 findUsingInfo() 方法:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        //省略部分代碼
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                 //👇關鍵代碼,從索引類中獲取 SubscriberMethod
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            }
            //省略部分代碼
        }
    }
複製代碼

subscriberInfo 不爲空時,會經過 getSubscriberMethods()方法,去獲取索引類中 SubscriberMethod[]數組 信息。由於索引類使用的是 SimpleSubscriberInfo 類,咱們查看該類中該方法的實現:

@Override
    public synchronized SubscriberMethod[] getSubscriberMethods() {
        int length = methodInfos.length;
        SubscriberMethod[] methods = new SubscriberMethod[length];
        for (int i = 0; i < length; i++) {
            SubscriberMethodInfo info = methodInfos[i];
            methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
                    info.priority, info.sticky);
        }
        return methods;
    }
複製代碼

觀察該代碼,咱們發現 SubscriberMethod 對象的建立是經過 createSubscriberMethod 方法建立的,咱們繼續跟蹤。

protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky) {
        try {
            Method method = subscriberClass.getDeclaredMethod(methodName, eventType);
            return new SubscriberMethod(method, eventType, threadMode, priority, sticky);
        } catch (NoSuchMethodException e) {
            throw new EventBusException("Could not find subscriber method in " + subscriberClass +
                    ". Maybe a missing ProGuard rule?", e);
        }
    }
複製代碼

從上述代碼中,咱們能夠看出 SubscriberMethod 中的 Method 對象,實際上是調用訂閱者的 class 對象並使用 getDeclaredMethod()方法找到的。

如今爲止咱們已經基本瞭解,索引類之因此相比傳統的經過反射遍歷去獲取訂閱方法效率要更高。是由於在自動生成的索引類中,已經包含了相關訂閱者中的訂閱方法的名稱及註解信息,那麼當 EventBus 註冊訂閱者時,就能夠直接經過方法名稱拿到 Method 對象。這樣就減小了經過遍歷尋找方法的時間。

索引類的生成

那如今咱們繼續學習 EventBus3 中是如何建立索引類的。索引類的建立是經過 APT 技術,若是你不瞭解這門技術,你可能須要查看文章 Android-註解系列之APT工具(三)

APT(Annotation Processing Tool)是 javac 中提供的一種編譯時掃描和處理註解的工具,它會對源代碼文件進行檢查,並找出其中的註解,而後根據用戶自定義的註解處理方法進行額外的處理。APT工具不只能解析註解,還能根據註解生成其餘的源文件,最終將生成的新的源文件與原來的源文件共同編譯(注意:APT並不能對源文件進行修改操做,只能生成新的文件,例如在已有的類中添加方法

使用APT技術須要建立本身的註解處理器,在 EventBus 中也建立了本身的註解處理器,從其源代碼中咱們就能夠看出。

EventBus註解處理器.png

那下面,咱們就直接查看源碼:

如下的代碼,都出至於 EventBusAnnotationProcessor

查看 EventBusAnnotationProcessor 中的 process() 方法:

process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv):註解處理器實際處理方法,通常要求子類實現該抽象方法,你能夠在在這裏寫你的掃描與處理註解的代碼,以及生成 Java 文件。其中參數 RoundEnvironment ,可讓你查詢出包含特定註解的被註解元素.

@SupportedAnnotationTypes(「org.greenrobot.eventbus.Subscribe」)
@SupportedOptions(value = {"eventBusIndex", "verbose」})
public class EventBusAnnotationProcessor extends AbstractProcessor {
public static final String OPTION_EVENT_BUS_INDEX = 「eventBusIndex」;

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        try {
            //步驟1:👇獲取咱們配置的索引類,
            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;
            }

            //省略部分代碼

            //步驟2:👇收集當前訂閱者信息
            collectSubscribers(annotations, env, messager);
            //步驟3:👇建立索引類文件
            if (!methodsByClass.isEmpty()) {
                createInfoIndexFile(index);
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found」);
            }
            writerRoundDone = true;
        } catch (RuntimeException e) {
            //省略部分代碼
        }
        return true;
    }
}
複製代碼

該方法中主要邏輯爲三個邏輯:

  • 步驟1:讀取咱們以前在 APP 中的 build.gradle 設置的索引類對應的包名與類名。
  • 步驟2:讀取源文件中的包含 @Subscribe 註解的方法。並將訂閱者與訂閱方法進行記錄在 methodsByClass Map 集合中。
  • 步驟3:根據讀取的索引類設置,經過 createInfoIndexFile() 方法開始建立索引類文件。

由於聲明瞭@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe") 在註解處理器上,那麼 APT 只會處理包含該註解的文件。

咱們接下來看看步驟2中的方法 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) {
                if (element instanceof ExecutableElement) {
                    ExecutableElement method = (ExecutableElement) element;
                    if (checkHasNoErrors(method, messager)) {
                        //獲取包含`@Subscribe`類的class對象
                        TypeElement classElement = (TypeElement) method.getEnclosingElement();
                        methodsByClass.putElement(classElement, method);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
                }
            }
        }
    }
複製代碼

在註解處理過程當中,咱們須要掃描全部的Java源文件,源代碼的每個部分都是一個特定類型的Element,也就是說 Element 表明源文件中的元素,例如包、類、字段、方法等。

在上述方法中,annotations 爲掃描到包含 @Subscribe 註解 的 Element 集合。其中 ExecutableElement 表示類或接口的方法、構造函數或初始化器(靜態或實例),由於咱們能夠經過 getEnclosingElement()方法,拿到當前 ExecutableElement 的最近的父 Element,那麼咱們就能得到當前的類的 element 對象了。那麼經過該方法,咱們就能知道全部訂閱者與其對應的訂閱方法了。

咱們繼續跟蹤查看索引類文件的建立:

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("    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
                }
            }
        }
    }
複製代碼

在該方法中,經過 processingEnv.getFiler().createSourceFile(index) 拿到咱們須要建立的索引類文件對象,而後經過文件IO流向該文件中輸入索引類中須要的內容。在該方法中,最爲主要的就是 writeIndexLines() 方法了。查看該方法:

private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
        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);
                //👇關鍵代碼
                writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
                writer.write("        }));\n\n」);
            } else {
                writer.write("        // Subscriber not visible to index: " + subscriberClass + "\n」);
            }
        }
    }
複製代碼

在該方法中,會從 methodsByClass Map 中遍歷獲取咱們以前的訂閱者,而後獲取其全部的訂閱方法,並書寫模板方法。其中關構造 SubscriberMethodInfo 代碼的關鍵方法爲 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();
            //獲取訂閱的事件class類型字符串信息
            String eventClass = getClassString(paramElement, myPackage) + ".class」;
            //獲取方法上的註解信息
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
            List<String> parts = new ArrayList<>();
            parts.add(callPrefix + "(\"" + methodName + "\",」);
            String lineEnd = "),」;
            //設置優先級,是否粘性,線程模式,訂閱事件class類型
            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() + ")」);
            }

        }
    }
複製代碼

在該方法中,會獲取訂閱方法的參數信息,並構建 SubscriberMethodInfo 信息。這裏就不對該方法進行詳細的介紹了,你們能夠根據代碼中的註釋進行理解。

混淆相關

在使用 EventBus3 的時候,若是你的項目採用了混淆,須要注意 keep 如下類及方法。官方中已經給出了詳細的 keep 規則,以下所示:

-keepattributes *Annotation*
-keepclassmembers class * {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}
複製代碼

爲何不能混淆註解

android在打包的時候,應用程序會進行代碼優化,優化的過程就把註解給去掉了。爲了在程序運行期間讀取到註解信息,因此咱們須要保存註解信息不被混淆。

爲何不能混淆包含 @Subscribe 註解的方法

由於當咱們在使用索引類時,獲取相關訂閱的方法是經過方法名稱獲取的,那麼當代碼被混淆事後,訂閱者的方法名稱將會發生改變,好比原來訂閱方法名稱爲onMessageEvent,混淆後有可能改成a,或b方法。這個時候是找不到相關的訂閱者的方法的 ,就會拋出 Could not find subscriber method in + subscriberClass + Maybe a missing ProGuard rule? 的異常,因此在混淆的時候咱們須要保留訂閱者全部包含 @Subscribe 註解的方法。

爲何不能混淆枚舉類中的靜態變量

若是咱們沒有在混淆規則中添加以下語句:

-keep public enum org.greenrobot.eventbus.ThreadMode { public static *; }
複製代碼

在運行程序的時候,會報java.lang.NoSuchFieldError: No static field POSTING。緣由是由於在 SubscriberMethodFinderfindUsingReflection 方法中,在調用 Method.getAnnotation()時獲取 ThreadMode 這個 enum 失敗了。

咱們都知道當咱們聲明枚舉類時,編譯器會爲咱們的枚舉,自動生成一個繼承 java.lang.Enumfinal 類。以下所示:

//使用命令 javap ThreadMode.class
public final class com.tian.auto.ThreadMode extends java.lang.Enum<com.tian.auto.ThreadMode> {
  public static final com.tian.auto.ThreadMode POSTING;
  public static final com.tian.auto.ThreadMode MAIN;
  public static final com.tian.auto.ThreadMode MAIN_ORDERED;
  public static final com.tian.auto.ThreadMode BACKGROUND;
  public static final com.tian.auto.ThreadMode ASYNC;
  public static com.tian.auto.ThreadMode[] values();
  public static com.tian.auto.ThreadMode valueOf(java.lang.String);
  static {};
}
複製代碼

也就是說,咱們在枚舉中聲明的元素,其實最後對應的是類中的靜態公有的常量。

那麼在結合在沒有添加混淆規則時,程序所提示的錯誤信息。咱們能夠肯定當咱們在註解中包含枚舉類型的註解元素時且設置了默認值時。該默認值是經過枚舉類的 class 對象.getField(String name) 去獲取的。由於只有該方法纔會拋出該異常。getField() 代碼以下所示:

public Field getField(String name)
        throws NoSuchFieldException {
        if (name == null) {
            throw new NullPointerException("name == null」);
        }
        Field result = getPublicFieldRecursive(name);
        if (result == null) {
            throw new NoSuchFieldException(name);
        }
        return result;
    }
複製代碼

那麼也就說若是不添加上述的 keep 規則,就會致使咱們編譯器自動生成的靜態常量名發生變化,又由於註解中的默認枚舉值,是經過 getField(String name) 得到的。因此就會出現找不到字段的狀況。

其實在不少狀況下,咱們須要添加 keep 規則,經常是由於代碼中是直接拿混淆前的方法名稱或字段名稱去直接尋找混淆後的方法與字段名稱,咱們只要在項目中注意這些狀況,添加相應的 keep 規則,就能夠避免由於代碼被混淆而產生的異常啦。

最後

EventBus3 中的索引類及其相關內容到這裏就講完啦!我相應你們已經瞭解了索引類在性能優化上的重要做用。但願你們在後續使用EventBus3時,必定要使用索引類呦。在接下來的一段時間內,我可能不會繼續更新博客啦,由於做者我要去學習 flutter 去啦~ 沒有辦法,總要保持前進呢。優秀的人還在努力,更況且本身並不聰明呢。哎~傷心

相關文章
相關標籤/搜索