EventBus源码分析(二):编译库源码解析

前言#

本来想把EventBus的使用和编译库的分析一起说,但是觉得篇幅有点太大了,编译库的东西虽然不多也不复杂,但是还是有很多能学习到的东西。

在上一篇已经建议大家对apt 和注解的使用有了一定的了解之后再来,否则你可能看的一脸懵逼,如果你还接触他们,可以先看一下我之前写的博客:

注解(Annotation)的基本了解

正文#

使用apt编译工具,主要是实现process(Set<? extends TypeElement> annotations, RoundEnvironment env)方法,这里完成了生成类和使用注解的所有工作:

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        try {
            // 通过命令行得到了EventBus的索引值,这个值通过apt工具类配置,用来找到程序的包名,也是文件要生成的位置
            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 = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
            // 此处通过字符串的截取得到了包名
            int lastPeriod = index.lastIndexOf('.');
            String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;

            round++;
            // 接下来进行了一些检查,这个就不用看了
            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.");
            }
            // 这里才是重点,开始收集Subscriber注解
            collectSubscribers(annotations, env, messager);
            // 开始检查某些注解是否要忽略
            checkForSubscribersToSkip(messager, indexPackage);
            // 检查是否被注解的方法的集合 是空的
            if (!methodsByClass.isEmpty()) {
                createInfoIndexFile(index);
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
            }
            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);
        }
        return true;
    }

这就是整个编译实现的过程,看着代码量很多,但是实际上我们关注的点只有几个,首先这个eventBusIndex参数是从哪来的呢?

String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);这个参数是在要运行的工程中配置的,你可以打开EventBusPerformance的build.gradle文件:
apt { arguments { eventBusIndex "org.greenrobot.eventbusperf.MyEventBusIndex" } }

我们重点要看的只有三个方法:

// 这里才是重点,开始收集Subscriber注解
collectSubscribers(annotations, env, messager);
// 开始检查某些注解是否要忽略
checkForSubscribersToSkip(messager, indexPackage);
// 检查是否被注解的方法的集合 是空的
if (!methodsByClass.isEmpty()) {
      // 开始创建文件
      createInfoIndexFile(index);
}

首先通过collectSubscribers(annotations, env, messager);收集所有的注解:

/**
     * 收集所有的注解
     * */
    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;
                    // 对这个方法进行必要的检查,不允许static/ 必须是public / 只能有一个参数
                    if (checkHasNoErrors(method, messager)) {
                        // 找到这个方法的类
                        TypeElement classElement = (TypeElement) method.getEnclosingElement();
                        // 把类个方法保存起来
                        methodsByClass.putElement(classElement, method);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
                }
            }
        }
    }

上面的注释已经写的非常详细了,找到所有被注解的方法,然后会这些方法进行检查,都做了哪些限制呢?

/**
     * 对方法进行检查
     * */
    private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
        // 不允许是static静态方法
        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;
    }

看来EventBus对于方法的限制还是很严格的,不能是static,只能是public,而且还只能由一个参数。

注解的收集工作就到此结束,然后对这个集合再次筛选,检查某些注解是否要忽略,进入到checkForSubscribersToSkip(messager, indexPackage):

private void checkForSubscribersToSkip(Messager messager, String myPackage) {
        // 遍历刚才获得类和其对应的注解的集合
        for (TypeElement skipCandidate : methodsByClass.keySet()) {
            // 得到类
            TypeElement subscriberClass = skipCandidate;
            // 开始循环判断判断,一直找到最顶端的父类
            while (subscriberClass != null) {
                // 检查类是否可见
                if (!isVisible(myPackage, subscriberClass)) {
                    // 如果类是不可见的,把他保存到不可见类的集合中
                    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);
                        // 如果参数不是类或者是接口,不会处理
                        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 = getSuperclass(subscriberClass);
            }
        }
    }

这个方法里把刚才收集到的注解和对应的类,进行遍历,从子类到最顶端的父类,先检查类是否是可见的,再检查参数的合法性,只允许是类或者是接口,这也解释了为什么使用基本类型的方法无法接受到Event。

下面是判断类的可见的方法:

/**
     * 判断一个类是否可见
     * */
    private boolean isVisible(String myPackage, TypeElement typeElement) {
        // 获取类的修饰符
        Set<Modifier> modifiers = typeElement.getModifiers();
        boolean visible;
        // 如果这个类是public的,返回true
        if (modifiers.contains(Modifier.PUBLIC)) {
            visible = true;
        }
        // 如果这个类是PRIVATE 或 PROTECTED 返回false
        else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
            visible = false;
        }
        // 其他情况
        else {
            // 获取完整的包名
            String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
            // 如果包名是空的,说明类是在最外层,是可见的
            if (myPackage == null) {
                visible = subscriberPackage.length() == 0;
            } else {
                // 判断包名是否和类的包名相同,同样是可见的
                visible = myPackage.equals(subscriberPackage);
            }
        }
        return visible;
    }

从方法中总结,只有三种情况类是可见的:

1、类是public修饰。
2、类在最外层,不在任何包内。
3、正好在编译的文件的包下。

2、3是在比较极端的情况下,我们平时注意使用注解的类都是public的就OK了。

然后就是最后的一步,生成我们的源文件createInfoIndexFile(index):

/**
     * 开始创建每个类的索引文件,也是生成的Java文件
     *
     *   @param index 文件生成的位置
     * */
    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);
            // 创建字符输出流
            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");
            // 创建一个私有的不可变的静态变量SUBSCRIBER_INDEX,类型是Map<Class<?>, SubscriberInfo>
            writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
            // static 方法块,用来初始化静态变量
            writer.write("    static {\n");
            writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
            
            // 这里把所有的类和方法都写到文件里了
            writeIndexLines(writer, myPackage);
            writer.write("    }\n\n");
            // 定义putIndex方法,刚才的writeIndexLines中就是使用了这个方法
            // 把我们的类名,还有注解的方法名,还有定义的优先级和线程信息都放入了集合中
            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
                }
            }
        }
    }

在文件中引入了几个文件,都是在EventBus中定义的,大家都去看一看,这里就略过了。我们已经知道这个文件中,有一个静态变量SUBSCRIBER_INDEX,并且有对应的put和get方法,然后再去看writeIndexLines(writer, myPackage):

private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
        // 开始遍历被注解的方法集合
        for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
            // 这里做了一个检查,查看是否这个方法被忽略了
            if (classesToSkip.contains(subscriberTypeElement)) {
                continue;
            }
            // 得到类名
            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");
            }
        }
    }

/**
     * 写入被注解的方法
     * */
    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<>();
            // 开始把字符放入到list中
            // 这是方法名
            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() + ")");
            }

        }
    }

这个方法代码很多但是逻辑很简单,就是把刚才经过层层筛选的注解个对应的类封装成SimpleSubscriberInfo对象,通过刚才的put方法,都保存到了SUBSCRIBER_INDEX中。

SimpleSubscriberInfo里面有类的信息,还有被注解的方法的信息,上面使用代码的形式定义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>();

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusBackground.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventBackgroundThread", TestEvent.class, ThreadMode.BACKGROUND),
        }));

        putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusMain.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread", TestEvent.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.SubscribeClassEventBusDefault.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEvent", TestEvent.class),
        }));

    }

    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;
        }
    }
}

ok,这样我们就知道了刚才的Java源文件生成的内容,整个编译库就到此结束了。

总结#

编译库的作用主要是:把类和其中被注解的方法,封装成一个SubscriberInfo信息保存起来,SubscriberInfo里面不仅有类的信息,还有被注解的方法的必要的设置,例如sticky,threadMode等等,这些信息都会为框架层的实现逻辑服务。

ok,编译库我们就已经完美攻克了,接下来就是看看再Java框架层的实现逻辑了。

因为篇幅问题,我会漏掉一些方法的说明,所以我把我的工程连接发给大家,方便大家学习使用:https://github.com/li504799868/EventBus-master

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707
  • 博文出处:EventBus源码解析,欢迎大家关注我的博客,谢谢! 0001B 时近年末,但是也没闲着。最近正好在看...
    俞其荣阅读 1,294评论 1 16
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • EventBus源码分析(二) 在之前的一篇文章EventBus源码分析(一)分析了EventBus关于注册注销以...
    蕉下孤客阅读 1,649评论 0 10
  • 01 有时候我觉得活着特别累。累到不知道为什么活着,累到什么都不想干,甚至不想活。 而其实我什么都没干。 我并不是...
    拒绝_1eca阅读 442评论 0 0