100行代码拆解EventBus核心逻辑(二)
- 2019 年 12 月 17 日
- 笔记
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。 Github:https://github.com/hylinux1024 微信公众号:终身开发者(angrycode)
问题
前面我们参考 EventBus
的实现逻辑模仿了一个最简单的 EasyBus
。这个其实也是观察者模式的实现,所以整体逻辑理解起来应该是不难的。在 EasyBus
中进行注册的时候是通过反射机制对观察者的信息进行注册,然后解析出监听接口和事件类型( onEventXXXMethod(MessageEvent)
方法以及其参数)。可以看出在这个注册的过程中,使用了运行时的反射机制,这在追求性能极致的基础组件中,这是可以优化的点。那么如何优化呢?这就需要用到今天提到的注解以及注解解析器的相关技术了。
注解
注解可以对包、类、接口方法、属性以及其它注解进行修饰,它是一个标记。就像所有的类继承于 Object
一样,所有的注解本质都是继承于 Annotation
这个接口。 如何定义注解呢? 这就需要使用元注解。元注解就是可以用来修饰其它注解的注解。 常见的元注解有
@Target
指定注解的作用目标。通过枚举ElementType
来指定修饰包、类、方法、属性或者其它注解。@Retention
指定注解的生命周期。同样通过枚举RetentionPolicy
进行配置。生命周期从短到长为RetentionPolicy.SOURCE
,RetentionPolicy.CLASS
,RetentionPolicy.RUNTIME
生命周期分别对应到源码文件、Class
文件以及运行时@Documented
指定注解是否被包含在JavaDoc
中@Inherited
指定注解是否能被子类继承
例如常见的注解有 @Override
、 @Deprecated
、 @FuncationalInterface
等
看一下 @Override
的定义
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
该注解作用在方法中,并且生命周期是在编译期间就会被丢弃
再看一个 EventBus
中定义的注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Subscribe { ThreadMode threadMode() default ThreadMode.POSTING; /** * If true, delivers the most recent sticky event (posted with * {@link EventBus#postSticky(Object)}) to this subscriber (if event available). */ boolean sticky() default false; /** Subscriber priority to influence the order of event delivery. * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of * delivery among subscribers with different {@link ThreadMode}s! */ int priority() default 0; }
该注解作用于方法,且生命周期是最长的 Runtime
阶段,这个注解还定义了两个属性 sticky
和 priority
分别表示这个事件是否是粘滞事件和事件的优先级。
注解解析器
在 EventBus
中默认是不使用注解解析器的,若要开启注解解析器生成索引辅助类,需要在 app/gradle
中配置 annotationProcessor
。
apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied dependencies { implementation 'org.greenrobot:eventbus:3.1.1' kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1' } kapt { arguments { arg('eventBusIndex', 'com.github.easybus.MyEventBusIndex') } }
由于我的 Demo
使用 Kotlin
编写,所以配置的是 kapt
。详细配置文档可以参考EventBus文档
通过 AS
编译之后,就会在 build/generated/source/kapt/debug/com/github/easybus
目录下生成索引文件 MyEventBusIndex
这个文件名称就是在 kapt
中配置的。
打开 MyEventBusIndex
源码
/** 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(com.github.easybus.demo.MainActivity.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onEventUpdate", com.github.easybus.demo.MessageEvent.class, ThreadMode.MAIN), new SubscriberMethodInfo("onEventUpdate2", com.github.easybus.demo.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; } } }
源码的逻辑很简单,内部是一个静态的 Map
类型的索引,存储了订阅者的信息,这个就是编译时动态生成的辅助类。在 static
代码块中,将订阅者 MainActivity
的信息 onEventUpdate
和 onEventUpdate2
封装到 SubscriberInfo
中。 然后就可以在项目中使用这个索引辅助类
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
或者全局配置
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus(); // Now the default instance uses the given index. Use it like this: EventBus eventBus = EventBus.getDefault();
通过它就可以避免在运行时对注解进行解析,使得基础组件的性能达到最佳。
总结
本文简单介绍了通过元注解自定义注解,以及在 EventBus
中是如何使用注解解析器提升性能。关于注解解析器的实现将在下一篇进行拆解。
引用
- http://greenrobot.org/eventbus/documentation/subscriber-index/
EventBus
关于配置订阅者索引的官方文档