专栏首页Android工程师的修仙之旅自己动手写事件总线(EventBus)
原创

自己动手写事件总线(EventBus)

事件总线核心逻辑的实现。

<!--more-->

# EventBus的作用

Android中存在各种通信场景,如`Activity`之间的跳转,`Activity`与`Fragment`以及其他组件之间的交互,以及在某个耗时操作(如请求网络)之后的callback回调等,互相之之间往往需要持有对方的引用,每个场景的写法也有差异,导致耦合性较高且不便维护。以`Activity`和`Fragment`的通信为例,官方做法是实现一个接口,然后持有对方的引用,再强行转成接口类型,导致耦合度偏高。再以`Activity`的返回为例,一方需要设置`setResult`,而另一方需要在`onActivityResult`做对应处理,如果有多个返回路径,代码就会十分臃肿。而`SimpleEventBus`(本文最终实现的简化版事件总线)的写法如下:

```java

public class MainActivity extends AppCompatActivity {

TextView mTextView;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mTextView = findViewById(R.id.tv_demo);

mTextView.setText("MainActivity");

mTextView.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

Intent intent = new Intent(MainActivity.this, SecondActivity.class);

startActivity(intent);

}

});

EventBus.getDefault().register(this);

}

@Subscribe(threadMode = ThreadMode.MAIN)

public void onReturn(Message message) {

mTextView.setText(message.mContent);

}

@Override

protected void onDestroy() {

super.onDestroy();

EventBus.getDefault().unregister(this);

}

}

```

来源`Activity`:

```java

public class SecondActivity extends AppCompatActivity {

TextView mTextView;

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mTextView = findViewById(R.id.tv_demo);

mTextView.setText("SecondActivity,点击返回");

mTextView.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

Message message = new Message();

message.mContent = "从SecondActivity返回";

EventBus.getDefault().post(message);

finish();

}

});

}

}

```

效果如下:

![](https://github.com/vimerzhao/images/raw/master/2018-12/eventbus-demo.gif)

似乎只是换了一种写法,但在场景愈加复杂后,`EventBus`能够体现出更好的解耦能力。

# 背景知识

主要涉及三方面的知识:

1. 观察者模式(or 发布-订阅模式)

2. Android消息机制

3. Java并发编程

本文可以认为是[greenrobot/EventBus](https://github.com/greenrobot/EventBus)这个开源库的源码阅读指南,笔者在看设计模式相关书籍的时候了解到这个库,觉得有必要实现一下核心功能以加深理解。

# 实现过程

`EventBus`的使用分三个步骤:注册监听、发送事件和取消监听,相应本文也将分这三步来实现。

## 注册监听

定义一个注解:

```

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Subscribe {

ThreadMode threadMode() default ThreadMode.POST;

}

```

`greenrobot/EventBus`还支持优先级和粘性事件,这里只支持最基本的能力:区分线程,因为如更新UI的操作必须放在主线程。`ThreadMode`如下:

```

public enum ThreadMode {

MAIN, // 主线程

POST, // 发送消息的线程

ASYNC // 新开一个线程发送

}

```

在对象初始化的时候,使用`register`方法注册,该方法会解析被注册对象的所有方法,并解析声明了注解的方法(即观察者),核心代码如下:

```

public class EventBus {

...

public void register(Object subscriber) {

if (subscriber == null) {

return;

}

synchronized (this) {

subscribe(subscriber);

}

}

...

private void subscribe(Object subscriber) {

if (subscriber == null) {

return;

}

// TODO 巨踏马难看的缩进

Class<?> clazz = subscriber.getClass();

while (clazz != null && !isSystemClass(clazz.getName())) {

final Method[] methods = clazz.getDeclaredMethods();

for (Method method : methods) {

Subscribe annotation = method.getAnnotation(Subscribe.class);

if (annotation != null) {

Class<?>[] paramClassArray = method.getParameterTypes();

if (paramClassArray != null && paramClassArray.length == 1) {

Class<?> paramType = convertType(paramClassArray[0]);

EventType eventType = new EventType(paramType);

SubscriberMethod subscriberMethod = new SubscriberMethod(method, annotation.threadMode(), paramType);

realSubscribe(subscriber, subscriberMethod, eventType);

}

}

}

clazz = clazz.getSuperclass();

}

}

...

private void realSubscribe(Object subscriber, SubscriberMethod method, EventType eventType) {

CopyOnWriteArrayList<Subscription> subscriptions = mSubscriptionsByEventtype.get(subscriber);

if (subscriptions == null) {

subscriptions = new CopyOnWriteArrayList<>();

}

Subscription subscription = new Subscription(subscriber, method);

if (subscriptions.contains(subscription)) {

return;

}

subscriptions.add(subscription);

mSubscriptionsByEventtype.put(eventType, subscriptions);

}

...

}

```

执行过这些逻辑后,该对象所有的观察者方法都会被存在一个Map中,其Key是`EventType`,即观察事件的类型,Value是订阅了该类型事件的所有方法(即观察者)的一个列表,每个方法和对象一起封装成了一个`Subscription`类:

```

public class Subscription {

public final Reference<Object> subscriber;

public final SubscriberMethod subscriberMethod;

public Subscription(Object subscriber,

SubscriberMethod subscriberMethod) {

this.subscriber = new WeakReference<>(subscriber);// EventBus3 没用弱引用?

this.subscriberMethod = subscriberMethod;

}

@Override

public int hashCode() {

return subscriber.hashCode() + subscriberMethod.methodString.hashCode();

}

@Override

public boolean equals(Object obj) {

if (obj instanceof Subscription) {

Subscription other = (Subscription) obj;

return subscriber == other.subscribe

&& subscriberMethod.equals(other.subscriberMethod);

} else {

return false;

}

}

}

```

如此,便是注册监听方法的核心逻辑了。

## 消息发送

消息的发送代码很简单:

```

public class EventBus {

...

private EventDispatcher mEventDispatcher = new EventDispatcher();

private ThreadLocal<Queue<EventType>> mThreadLocalEvents = new ThreadLocal<Queue<EventType>>() {

@Override

protected Queue<EventType> initialValue() {

return new ConcurrentLinkedQueue<>();

}

};

...

public void post(Object message) {

if (message == null) {

return;

}

mThreadLocalEvents.get().offer(new EventType(message.getClass()));

mEventDispatcher.dispatchEvents(message);

}

...

}

```

比较复杂一点的是需要根据注解声明的线程模式在对应的线程进行发布:

```

public class EventBus {

...

private class EventDispatcher {

private IEventHandler mMainEventHandler = new MainEventHandler();

private IEventHandler mPostEventHandler = new DefaultEventHandler();

private IEventHandler mAsyncEventHandler = new AsyncEventHandler();

void dispatchEvents(Object message) {

Queue<EventType> eventQueue = mThreadLocalEvents.get();

while (eventQueue.size() > 0) {

handleEvent(eventQueue.poll(), message);

}

}

private void handleEvent(EventType eventType, Object message) {

List<Subscription> subscriptions = mSubscriptionsByEventtype.get(eventType);

if (subscriptions == null) {

return;

}

for (Subscription subscription : subscriptions) {

IEventHandler eventHandler = getEventHandler(subscription.subscriberMethod.threadMode);

eventHandler.handleEvent(subscription, message);

}

}

private IEventHandler getEventHandler(ThreadMode mode) {

if (mode == ThreadMode.ASYNC) {

return mAsyncEventHandler;

}

if (mode == ThreadMode.POST) {

return mPostEventHandler;

}

return mMainEventHandler;

}

}// end of the class

...

}

```

三种线程模式分别如下,`DefaultEventHandler`(在发布线程执行观察者放方法):

```

public class DefaultEventHandler implements IEventHandler {

@Override

public void handleEvent(Subscription subscription, Object message) {

if (subscription == null || subscription.subscriber.get() == null) {

return;

}

try {

subscription.subscriberMethod.method.invoke(subscription.subscriber.get(), message);

} catch (IllegalAccessException | InvocationTargetException e) {

e.printStackTrace();

}

}

}

```

`MainEventHandler`(在主线程执行):

```

public class MainEventHandler implements IEventHandler {

private Handler mMainHandler = new Handler(Looper.getMainLooper());

DefaultEventHandler hanlder = new DefaultEventHandler();

@Override

public void handleEvent(final Subscription subscription, final Object message) {

mMainHandler.post(new Runnable() {

@Override

public void run() {

hanlder.handleEvent(subscription, message);

}

});

}

}

```

`AsyncEventHandler`(新开一个线程执行):

```

public class AsyncEventHandler implements IEventHandler {

private DispatcherThread mDispatcherThread;

private IEventHandler mEventHandler = new DefaultEventHandler();

public AsyncEventHandler() {

mDispatcherThread = new DispatcherThread(AsyncEventHandler.class.getSimpleName());

mDispatcherThread.start();

}

@Override

public void handleEvent(final Subscription subscription, final Object message) {

mDispatcherThread.post(new Runnable() {

@Override

public void run() {

mEventHandler.handleEvent(subscription, message);

}

});

}

private class DispatcherThread extends HandlerThread {

// 关联了AsyncExecutor消息队列的Handle

Handler mAsyncHandler;

DispatcherThread(String name) {

super(name);

}

public void post(Runnable runnable) {

if (mAsyncHandler == null) {

throw new NullPointerException("mAsyncHandler == null, please call start() first.");

}

mAsyncHandler.post(runnable);

}

@Override

public synchronized void start() {

super.start();

mAsyncHandler = new Handler(this.getLooper());

}

}

}

```

以上便是发布消息的代码。

## 注销监听

最后一个对象被销毁还要注销监听,否则容易导致内存泄露,目前`SimpleEventBus`用的是`WeakReference`,能够通过GC自动回收,但不知道`greenrobot/EventBus`为什么没这样实现,待研究。注销监听其实就是遍历Map,拿掉该对象的订阅即可:

```

public class EventBus {

...

public void unregister(Object subscriber) {

if (subscriber == null) {

return;

}

Iterator<CopyOnWriteArrayList<Subscription>> iterator = mSubscriptionsByEventtype.values().iterator();

while (iterator.hasNext()) {

CopyOnWriteArrayList<Subscription> subscriptions = iterator.next();

if (subscriptions != null) {

List<Subscription> foundSubscriptions = new LinkedList<>();

for (Subscription subscription : subscriptions) {

Object cacheObject = subscription.subscriber.get();

if (cacheObject == null || cacheObject.equals(subscriber)) {

foundSubscriptions.add(subscription);

}

}

subscriptions.removeAll(foundSubscriptions);

}

// 如果针对某个Event的订阅者数量为空了,那么需要从map中清除

if (subscriptions == null || subscriptions.size() == 0) {

iterator.remove();

}

}

}

...

}

```

以上便是事件总线最核心部分的代码实现,完整代码见[vimerzhao/SimpleEventBus](https://github.com/vimerzhao/SimpleEventBus),后面发现问题更新或者进行升级也只会改动仓库的代码。

# 局限性

由于时间关系,目前只研究了`EventBus`的核心部分,还有几个值得深入研究的点,在此记录一下,也欢迎路过的大牛指点一二。

## 性能问题

实际使用时,注解和反射会导致性能问题,但`EventBus3`已经通过`Subscriber Index`基本解决了这一问题,实现也非常有意思,是通过注解处理器(`Annotation Processor`)把耗时的逻辑从运行期提前到了编译期,通过编译期生成的索引来给运行期提速,这也是这个名字的由来。

## 可用性问题

如果订阅者很多会不会影响体验,毕竟原始的方法是点对点的消息传递,不会有这种问题,如果部分订阅者尚未初始化怎么办。等等。目前`EventBus3`提供了优先级和粘性事件的属性来进一步满足开发需求。但是否彻底解决问题了还有待验证。

## 跨进程问题

`EventBus`是进程内的消息管理机制,并且从开源社区的反馈来看这个项目是非常成功的,但稍微有点体量的APP都做了进程拆分,所以是否有必要支持多进程,能否在保证性能的情况下提供同等的代码解耦能力,也值得继续挖掘。目前有[lsxiao/Apollo](https://github.com/lsxiao/Apollo)和[Xiaofei-it/HermesEventBus](https://github.com/Xiaofei-it/HermesEventBus)等可供参考。

# 参考

- [greenrobot/EventBus](https://github.com/greenrobot/EventBus)

- [hehonghui/AndroidEventBus](https://github.com/hehonghui/AndroidEventBus)

- 《Android开发艺术探索》第十章

原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

登录 后参与评论
0 条评论

相关文章

  • 自己实现事件总线-EventBus事件总线的使用

    在C#中,我们可以在一个类中定义自己的事件,而其他的类可以订阅该事件,当某些事情发生时,可以通知到该类。这对于桌面应用或者独立的windows服务来说是非常有用...

    架构师精进
  • Guava - EventBus(事件总线)

    Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用...

    哲洛不闹
  • Vue事件总线(EventBus)使用详细介绍

    vue组件非常常见的有父子组件通信,兄弟组件通信。而父子组件通信就很简单,父组件会通过 props 向下传数据给子组件,当子组件有事情要告诉父组件时会通过 $e...

    Javanx
  • Android事件总线(二)EventBus3.0源码解析

    前言 上一篇我们讲到了EventBus3.0的用法,这一篇我们来讲一下EventBus3.0的源码以及它的利与弊。 1.构造函数 当我们要调用EventBus的...

    用户1269200
  • android事件总线EventBus3.0使用方法详解

    Event:事件,可以是任意类型的对象。 Subscriber:事件订阅者,在EventBus3.0之前消息处理的方法只能限定于onEvent、onEvent...

    砸漏
  • Android事件总线(一)EventBus3.0用法全解析

    前言 EventBus是一款针对Android优化的发布/订阅事件总线。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅,以及将发送者...

    用户1269200
  • Guava之eventBus异步事件总线的使用及源码分析

        List-1.1中,方法subscribe是接收者,方法test_sendMsg中post消息后,方法subscribe就会收到消息。这是因为方法sub...

    克虏伯
  • vue 事件总线EventBus的概念、使用以及注意点

    vue组件中的数据传递最最常见的就是父子组件之间的传递。父传子通过props向下传递数据给子组件;子传父通过$emit发送事件,并携带数据给父组件。而有时两个组...

    @零一
  • .Net Core微服务入门全纪录(六)——EventBus-事件总线

    上一篇【.Net Core微服务入门全纪录(五)——Ocelot-API网关(下)】中已经完成了Ocelot + Consul的搭建,这一篇简单说一下Event...

    xhznl
  • Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线

    Go beyond yourself rather than beyond others. 上篇文章 深入理解 EventBus 3.0 之使用篇 我们了解了 ...

    张拭心 shixinzhang
  • [项目更新] 集成RabbitMQ队列与EventBus总线

    终于项目继续迭代更新了,在开源这两年多,也是感谢每一个支持Blog.Core项目的同学,同时也感谢每一个在生产环境中使用,并提出意见和建议的小伙伴,2,606个...

    老张的哲学
  • 自己动手写客户端UI库——事件机制(设计思路大放送)

    在上一篇文章中我们创建了一个Button控件,并把这个控件显示在界面上, 在这一篇文章中,我们将为这个控件增加一个事件和一个方法 一:怎么绑定事件的问题 ...

    liulun
  • 【设计模式】692- TypeScript 设计模式之发布-订阅模式

    在之前两篇自测清单中,和大家分享了很多 JavaScript 基础知识,大家可以一起再回顾下~

    pingan8787
  • eShopOnContainers 知多少[5]:EventBus With RabbitMQ

    事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉。事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的...

    圣杰
  • EventBus源码学习笔记(一)

    EventBus 深入学习一 EventBus是一个消息总线,以观察者模式实现,用于简化程序的组件、线程通信,可以轻易切换线程、开辟线程; 传统上,Java...

    一灰灰blog
  • Flutter之EventBus消息总线

    原生开发中,时常遇到通知或广播机制,应对需要跨页面的事件通知。作为移动端跨平台框架的Flutter而言,也有同样的解决方案-EventBus,event_bus...

    Qson
  • Android 框架学习1:EventBus 3.0 的特点与如何使用

    前面总结了几篇基础,在这过程中看着别人分享自定义 View、架构或者源码分析,看起来比我写的“高大上”多了,内心也有点小波动。 但是自己的水平自己清楚,基础不扎...

    张拭心 shixinzhang
  • AKKA中的事件流

    在《企业应用集成模式》一书中,定义了许多与消息处理有关的模式,其中运用最为广泛的模式为Publisher-Subscriber模式,尤其是在异步处理场景下。 基...

    张逸

扫码关注腾讯云开发者

领取腾讯云代金券