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 去啦~ 没有办法,总要保持前进呢。优秀的人还在努力,更况且本身并不聪明呢。哎~伤心

相关文章
相关标签/搜索