浅析EventBus 3.0实现思想
xinqiu1009
9年前
<p>最近接触了EventBus,也看了一些源码分析的文章。在此就不再细述其代码的实现细节,主要针对其的设计思想做一些记录,也是自己思考的过程。同时本文尽量以较少的代码来将其主要设计思想说的透彻明白,不会针对细节做过多深入。</p> <h2>基本的事件发布订阅的实现</h2> <p>一般情况下,事件发布订阅机制都是跟观察者模式紧密相连。事件的发布中心都会维持着一组当前的观察者(也可叫做订阅者),这里称之为事件总线,(观察者的注册/取消则对应着在这组数据中进行添加和删除)。另外被观察者(也可叫发布者)则通过发出事件,事件总线拿到该事件,则在观察者列表中根据事件来查找相应的事件观察者,紧接着执行观察者的行为即可。对应一个简单的事件总线图如下:</p> <p><img src="https://simg.open-open.com/show/eb75ae4a4482211a5f8692238d7c7366.png"></p> <h2>EventBus 3.0的实现</h2> <h3>1. EventBus的基本使用</h3> <ul> <li>发布事件:</li> </ul> <pre> EventBus.post(Object event); </pre> <ul> <li>订阅事件:</li> </ul> <pre> @Subscribe public void subscribe(Object event){ } </pre> <ul> <li>订阅事件的注册以及取消注册:</li> </ul> <pre> EventBus.register(Object); EventBus.unregister(Object); </pre> <p>由上可见,EventBus的使用,还是相当简单的。其中添加了 @subscribe 注解的方法,则代表着真正的事件订阅者,另外,添加了注解的方法,必须通过 register 和 unregister 来进行订阅和取消订阅,它俩相应的参数 Object 则指代的是 @subscribe 方法所在的类。 另可看出,事件订阅参数 Object event ,即是我们所在事件总线中传递使用事件的参数。当我们要确定这个事件被接受到,则需要保持event的 class 是相同的。</p> <h3>2. EventBus的事件总线</h3> <p>首先需要明白的是在EventBus中,真正对应的订阅对象是 SubscriberMethod ,其包含了相应的 Method 类,以及事件参数类型 Class<?> eventType ,其他就是线程,优先级,是否Sticky信息。</p> <pre> /** Used internally by EventBus and generated subscriber indexes. */ public class SubscriberMethod { final Method method; final ThreadMode threadMode; final Class<?> eventType; final int priority; final boolean sticky; } </pre> <p>紧接着,谈及事件总线,一般都对应着一个集合,而EventBus中使用的是:</p> <pre> private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; </pre> <p>这里的Map集合采用的是一个HashMap集合,map的key对应就是之前 SubscriberMethod 中的 eventType , value则对应着一个线程安全的List,List中存放的是包含订阅对象 Object 及相应订阅方法 SubscriberMethod 的 Subscription 类:</p> <pre> final class Subscription { final Object subscriber; final SubscriberMethod subscriberMethod; } </pre> <p>现在,就很明了了,List数组用来维持所有当前的订阅者,即对应着我们经常所说的事件总线中的集合。</p> <h3>3. EventBus的事件订阅/消费</h3> <ul> <li>EventBus中的订阅与取消订阅 –> register / unregister</li> </ul> <p>1) <strong>订阅类信息的封装:</strong> EventBus的 register 方法,则会将参数 Object类中 @subscribe 注解的所有方法,逐一封装为 SubscriberMethod ,再与参数 subscriber 统一封装为 Subscription ,这样因注解方法可以多个,而通过订阅类的register的方法,最后得到的将是一组订阅者。</p> <p>2) <strong>订阅者的添加:</strong> EventBus类会将这组订阅者,根据不同的 eventType 参数,将放置在上文提到的 subscriptionsByEventType 结构中。这样,一个事件中心的机制就完成了,注册事件时,就在List中添加方法订阅者;取消注册事件,同时也是针对这个List中移除的订阅类Object对象中相应的 subscription 。</p> <ul> <li>EventBus事件的消费/发布事件 这里通过EventBus的 post(Object event) 方法,进行事件的发出。紧接着EventBus的总线LIst中找出订阅了这个event的方法 Subscription ,然后根据method指定的不同线程信息,将这个方法的调用,放置在相应线程中调用:</li> </ul> <pre> subscription.subscriberMethod.method.invoke(subscription.subscriber, event); </pre> <p>可以看出,订阅者已经被封装地非常完美,这样,我们在使用不同的线程调度策略就很简单了,随意指定一个 ThreadMode 即可在指定线程中调用。唯一不完美的地方,就是这里的调用时通过 Method 类调用的,肯定没有直接通过类调用来的直接,不过相对其带我们的好处来说,这点性能影响可以忽略不计了。</p> <h3>4. 优化之EventBusIndex</h3> <p>到这里,会发现有个很大的疑问: SubscriberMethod 信息是怎么生成的?答案很肯定的嘛,就是加了注解 @subscribe 的方法嘛。我们可以通过在运行时通过采用反射的方法,获取相应添加了注解的方法,再封装成为 SubscriberMethod 。而对我们开发者来说,使用反射带来的性能消耗,不由得我们不慎重一二的。 在3.0的版本,EventBus加入了apt处理的逻辑,有个 Subscriber Index 的介绍,主要是通过Apt在编译期根据注解直接生成相应的信息,来避免在运行时通过反射来获取。使用方法如下:</p> <pre> apt { arguments { eventBusIndex "com.example.myapp.MyEventBusIndex" } } </pre> <p>配置Index的调用如下:</p> <pre> EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build(); </pre> <p>之后,我们在执行了代码的编译之后,会生成一个包名为 com.example.myapp 的类 MyEventBusIndex 。这个类会实现一个如下的接口:</p> <pre> public interface SubscriberInfoIndex { SubscriberInfo getSubscriberInfo(Class<?> subscriberClass); } </pre> <p>从接口中可以看出,这个Index类只提供了一个方法 getSubsriberInfo ,这个方法需要我们传入订阅者所在的class类,然后获得一个 SubscriberInfo 的类,其类结构如下:</p> <pre> public interface SubscriberInfo { Class<?> getSubscriberClass(); SubscriberMethod[] getSubscriberMethods(); SubscriberInfo getSuperSubscriberInfo(); boolean shouldCheckSuperclass(); } </pre> <p>从接口中暴露的方法,即可获取所需的所有 SubscriberMethod 信息。而这只是一个获取单个类的方法,而apt生成的EventBusIndex中,会将所有的这些class及SubscriberInfo保存在静态变量hashMap结构,这样就达到了避免运行期反射获取生成订阅方法的性能问题。而变成另外一个流程:订阅类在注册的时候,直接通过HashMap中的订阅类,获取到 SubscriberInfo ,进而获取到所有的 SubscerberMethod ,并封装为 Subscription ,被添加到事件总线中。这样,在引入了apt之后,EventBus的性能问题就不需要我们担心了。(PS:在EventBus作者的博客也提及到了这一点,使用apt了的EventBus在性能表现这方便,犹如打鸡血一般。)</p> <h2>总结</h2> <p>EventBus将订阅者巧妙地转换为通过注解 @subscriber 定义的方法,方法的参数定义为事件所使用的数据类型,另外,可以指定订阅者不同的线程及优先级,给我们开发者带来最大的好处就是使用非常简单方便。这就不得不提及一些不好的地方了,可以看到的是其在设计实现的过程中,引入了许多中间对象,若是我们对内存使用及性能非常敏感的话,则必须自己实现一个更加轻量级的事件总线了。</p> <h2>后续</h2> <p>因这是一篇简单的介绍EventBus3.0设计思想实现的文章,但在查看其源码的过程中,也发现了其他很有意思的技术实现细节及线程处理等内容,将在下一篇中做介绍,欢迎关注。</p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959673676050439612" rel="nofollow">http://alighters.com/blog/2016/05/22/eventbus3-dot-0-analyze/</a></p> <p> </p>