文章目录
  1. 1. 一、简单使用
    1. 1.1. 1.1 新版本使用
    2. 1.2. 1.2 旧版本使用
  2. 2. 二、解决什么问题
    1. 2.1. 2.1 通信解耦
    2. 2.2. 2.2 线程切换

最近看了一下EventBus实现的源码,分享一下自己的心得体会。

EventBus这个库实现很简单,如官方所说一样,库体积很小。将组件之间的通信通过解耦的方式表现的淋漓尽致,废话不多说,EventBus优点官网上列举了一堆,有兴趣的可以去看一下http://greenrobot.org/eventbus/

分享主要有以下几个方面:

1)简单使用

  • 简单的说明EventBus的使用

2)解决什么问题

  • 比如解决了组件之间解耦问题,线程切换问题。

3)实现细节

  • EventBus是怎样注册订阅者,怎样通过post将消息发送到订阅者。

4)优化

  • EventBus中使用的一些细节优化,考虑很周全。

5)问题

  • 针对EventBus中使用的一些问题。

中间可能会介绍新旧版本的区别,新旧版本指的是3.0及以后的版本和以前的版本。要注意的是新旧版本不仅是版本号的区别,包名也不一样。旧版本的包名是de.greenrobot.even,新版本的包名是org.greenrobot.eventbu

一、简单使用

1.1 新版本使用

  • 注册消息订阅者,代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class EbNewActivity extends BaseNewActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_eb_new);

// 注册必须在Fragment初始化之前,因为订阅消息发送是在Fragment中
EventBus.getDefault().register(this);

getSupportFragmentManager()
.beginTransaction()
.add(R.id.frame_content, EbNewFragment.newInstance())
.commit();
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventBusMainEvent(String msg) {
Log.d("onEventBusMainEvent", Thread.currentThread().getName() + "," + msg);
}

// 默认是ThreadMode.POSTING,注意这里参数是Object,与其它不同
@Subscribe
public void onEventBusPostEvent(Object msg) {
Log.i("onEventBusPostEvent", Thread.currentThread().getName() + "," + msg);
}

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onEventBusBackgroundEvent(String msg) {
Log.w("onEventBusBgEvent", Thread.currentThread().getName() + "," + msg);
}

@Subscribe(threadMode = ThreadMode.ASYNC)
public void onEventBusAsyncEvent(String msg) {
Log.e("onEventBusAsyncEvent", Thread.currentThread().getName() + "," + msg);
}

@Override
protected void onDestroy() {
super.onDestroy();
// 注销,否则会导致Activity内存泄露
EventBus.getDefault().unregister(this);
}

}

activity_eb_new.xml布局文件很简单,里面只有一个LinearLayout,代码如下所示:

1
2
3
4
5
6
7
8
9

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/frame_content"
/>

@Subscribe注解除了指定EventBus的线程模型,还可以指定订阅者的优先级以及是否为sticky模式。

优先级的意思是当EventBus#post消息时,根据订阅的方法的优先级来执行相应的方法。优先级高的即priority的值越大的,先收到订阅消息。在注册订阅者时,就会根据这个priority的大小对顺序进行排序。代码如下所示:

org.greenrobot.eventbus.EventBus#subscribe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}

int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
// 根据优先级进行排序
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}

}

stick模式是指当EventBus#postSticky消息时,该消息会被缓存,当下次调用EventBus#register注册订阅者后,会把相应接收消息的方法都执行一遍。

可能描述太抽象,举个例子:一个项目组在一个房间封闭开发项目,其中测试人员和UI设计都不在。这个时候产品经理跑过来说,改个需求,这时候只有开发人员都知道。产品经理口头通知改需求就相当于post,发送的消息只有已经注册过接收者接收。产品经理把要改的需求打印到一张纸上,然后粘在门上,并向房间里面的开发人员描述了修改的需求。测试人员和UI设计回来之后,发现粘在门上的需求变更通知,也都知道了。产品经理把修改的需求粘在门上并向室内开发人员描述修改的需求这个过程就相当于postSticky,后面注册的接收者也会收到这个消息,先注册的就不用说了。

postSticky可以用来做延迟接收消息。下面看下源码的实现:

postSticky操作:

1)缓存消息

org.greenrobot.eventbus.EventBus#postSticky

1
2
3
4
5
6
7
8
public void postSticky(Object event) {
synchronized (stickyEvents) {
// 这里会缓存消息
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}

2)接收消息

org.greenrobot.eventbus.EventBus#register

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 这里处理订阅的方法
subscribe(subscriber, subscriberMethod);
}
}
}

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// ... 中间代码省略
if (subscriberMethod.sticky) {
// 重点看这里
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
// 执行消息会到这里
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
// 执行消息会到这里
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
// 执行消息或者加入消息队列中
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}

postToSubscription方法中会根据线程模型来执行订阅的方法。也就是后注册进来的订阅者也会收到之前的postStick消息。

  • 线程模型

EventBus的线程模型有四种,分别如下所示:

1
2
3
4
5
6
public enum ThreadMode {
POSTING,
MAIN,
BACKGROUND,
ASYNC
}

POSTING表示在当前线程执行,也就是post方法在哪个线程中执行,对应的接收消息的方法就在哪个线程执行;
MAIN表示接收的消息在UI线程中执行;
BACKGROUND表示接收的消息在非UI线程中执行,如果当前是在非UI线程中执行post,则会在当前线程中执行接收消息的方法;如果是在UI线程中执行post方法,则会在非UI线程中执行接收消息的方法。
ASYNC表示在与执行post线程不同的另外一个非UI线程中执行接收消息的方法。

上面的解释可能不太好理解,上面的代码日志输出如下所示:

1
2
3
4
onEventBusAsyncEvent: pool-1-thread-1,onAttach
onEventBusMainEvent: main,onAttach
onEventBusBgEvent: pool-1-thread-2,onAttach
onEventBusPostEvent: main,onAttach

对比下日志前面的线程名称就清楚了。

  • 发送消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EbNewFragment extends Fragment {

public static EbNewFragment newInstance() {
EbNewFragment fragment = new EbNewFragment();
return fragment;
}

@Override
public void onAttach(Context context) {
super.onAttach(context);
// 发送消息
EventBus.getDefault().post("onAttach");
}
}

因为在Activity中注册的方法接收的消息类型是StringObject(eventInheritance参数为true,第二篇会解释),所以这里post("onAttach")后,那些方法都会收到消息。

1.2 旧版本使用

老版本接收消息的订阅方法都是以onEvent开头,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class EbOldActivity extends Activity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_eb_old);
EventBus.getDefault().register(this);

getSupportFragmentManager()
.beginTransaction()
.add(R.id.frame_content, EbOldFragment.newInstance())
.commit();
}

public void onEvent(String msg) {
log("onEvent", msg);
}

public void onEventMainThread(String msg) {
log("onEventMainThread", msg);
}

public void onEventBackgroundThread(String msg) {
log("onEventBackgroundThread", msg);
}

public void onEventAsync(String msg) {
log("onEventAsync", msg);
}

@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}

activity_eb_old.xml布局文件内容与上面的activity_eb_new.xml布局文件类似。

onEvent后缀表示线程对应的线程模型,对应关系看下面的源码就清楚了:

de.greenrobot.event.SubscriberMethodFinder#findSubscriberMethods部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
// ON_EVENT_METHOD_NAME为onEvent
String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
ThreadMode threadMode;
if (modifierString.length() == 0) {
// onEvent方法
threadMode = ThreadMode.PostThread;
} else if (modifierString.equals("MainThread")) {
// onEventMainThread方法
threadMode = ThreadMode.MainThread;
} else if (modifierString.equals("BackgroundThread")) {
// onEventBackgroundThread方法
threadMode = ThreadMode.BackgroundThread;
} else if (modifierString.equals("Async")) {
// onEventAsync方法
threadMode = ThreadMode.Async;
} else {
if (skipMethodVerificationForClasses.containsKey(clazz)) {
continue;
} else {
throw new EventBusException("Illegal onEvent method, check for typos: " + method);
}
}

// ...
}
}
}

从上面源码中也可以看出来注册的订阅方法有一些要求:

  • 方法必须是public,不能是抽象和静态的;
  • 方法参数只能有一个。
  • 其它方法不能以onEvent开头,或者必须通过EventBusBuilder来创建EventBus实例,并且调用EventBusBuilder#skipMethodVerificationFor方法注册class,以忽略当前包含其它以onEvent开头的方法。

整个调用流程如下图所示:

EventBus注册、发送消息流程

OK,简单使用介绍完了,下面看看解决了什么问题。

二、解决什么问题

通过上面的使用,很明显知道EventBus解决了什么问题。

2.1 通信解耦

通过EventBus.register来注册订阅者,通过注解反射的方式提取接收消息的方法,再通过EventBus.post发送消息,从接收消息方法的缓存列表中找到对应消息接收者进行消息处理。这个时候也是通过反射调用方法。

所以这里是通过反射的方式来做到解耦。

2.2 线程切换

线程模型在老版本上是通过后缀来区别,新版本是以注解的方式来指定的,这样为线程切换提供很大的便利。EventBus自己提供了一个线程池来管理线程,ASYNCBACKGROUND标记的方法都是在同一个线程池中的线程来执行的。

另外就是EventBus还提供了一个消息队列,发送的消息是先进消息队列还是立及执行,这个是根据线程模型来的。部分源码如下所示:

org.greenrobot.eventbus.EventBus#postToSubscription

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
// 立及执行
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
// 立及执行
invokeSubscriber(subscription, event);
} else {
// UI线程的消息队列
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
// 后台线程的消息队列
backgroundPoster.enqueue(subscription, event);
} else {
// 立及执行
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
// 异步线程的消息队列
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}

消息队列有三种,UI线程执行消息的消息队列、后台线程的消息队列、异步线程的消息队列。


这部分写到这里,未完待续。

第二篇看这里EventBus实现分析(下)

文章目录
  1. 1. 一、简单使用
    1. 1.1. 1.1 新版本使用
    2. 1.2. 1.2 旧版本使用
  2. 2. 二、解决什么问题
    1. 2.1. 2.1 通信解耦
    2. 2.2. 2.2 线程切换