湖畔镇

EventBus总结

EventBus是为Android定制的发布/订阅总线

它可以简化组件之间的通信,解耦事件发送者和接受者,可以很好的与Activity、Fragment、后台线程协同工作,避免了复杂的依赖和生命周期相关的问题

广播稍显麻烦,并且传递对象时需要序列化

用法

基本

自定义事件类

1
2
3
public class MessageEvent {

}

注册和注销订阅者

1
2
EventBus.getDefault().register(this);
EventBus.getDefault().unregister(this);

添加处理函数

1
2
3
4
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {

}

EventBus3中处理函数可以任意取名,通过@Subscribe注解标记

threadMode:线程模式:

  • ThreadMode.POSTING 发送事件和接收事件在同一个线程
  • ThreadMode.MAIN 事件处理在UI线程进行
  • ThreadMode.BACKGROUND 如果是UI线程发送,则新建子线程执行,如果本身是后台线程发送,就在该线程执行
  • ThreadMode.ASYNC 无论从哪个线程发送,都在新线程执行

sticky:粘性事件,表示在订阅之前发布的事件订阅后也能收到

priority:优先级,默认为0,越大优先级越高

发送事件

1
EventBus.getDefault().post(new MessageEvent());

发送粘性事件

1
EventBus.getDefault().postSticky(new MessageEvent());

配置注解处理器获得索引

修改build.gradle

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

apt {
    arguments {
        // 这个就是生成的索引类的名字
        eventBusIndex 'com.github.lakeshire.eventbusdemo.MyEventBusIndex'
    }
}

编译期生成的类是这样的

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
/** 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(MainActivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onMessageEvent", MessageEvent.class, ThreadMode.MAIN, 100, false),
new SubscriberMethodInfo("onMessageEventAlt", Misc.class, ThreadMode.MAIN, 100, false),
}));

putIndex(new SimpleSubscriberInfo(ReceiveActivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onMessageEvent", MessageEvent.class, ThreadMode.MAIN, 0, true),
}));

}

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

然后设置默认的EventBus

1
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

用下来,注册时间用反射时是8ms,用索引时是2ms,再次注册是1ms,区别很明显

源码

EventBus

提供一个默认的单例实现,也可以通过Builder自定义

1
2
3
4
5
6
7
8
9
10
11
//  事件类型缓存
private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();

// 事件类型-订阅者表
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

// 订阅者-事件类型表
private final Map<Object, List<Class<?>>> typesBySubscriber;

// 粘性事件列表
private final Map<Class<?>, Object> stickyEvents;

有几个重要的数据容器

注册

1
2
3
4
5
6
7
8
9
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}

首先通过一个订阅方法查找器找到对应这个订阅者的一组方法,查找器内部会保存一份方法缓存表,如果已存在直接返回,没有的话就要实际查找,有反射和索引两种查找方式,索引在编译期通过注解处理器获得,速度要比反射快

1
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
  1. 查看subscriptionsByEventType中对应EventType的列表,根据优先级把新的订阅者添加进去
  2. 查看typesBySubscriber中对应Subscriber的列表,添加新的EventType
  3. 处理粘性事件,从粘性事件列表中获取对应EventType的事件,做一次发送
    这里就是发送粘性事件的时机,所以注册前发送的粘性事件再注册后可以接收到,粘性事件列表维护的是事件类型-事件(不是列表)对,发送后不会主动移除掉,所以再次注册还是会发送

注销

就是清理subscriptionsByEventTypetypesBySubscriber这两个列表

发送事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);

if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}

发送是向线程本地变量里维护的队列中添加一个事件,如果没有在发送则发送队列头的事件

postSingleEventForEventType()里在subscriptionsByEventType中查找对应EventType的订阅者,有的话就对每个订阅者发送

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

这个就是发送的核心部分,根据线程模式决定做法,跟线程模式的定义匹配:

  • POSTING直接调用订阅者的方法
  • MAIN判断是否在主线程,在主线程直接调用,否则放进主线程的消息队列
  • BACKGROUND判断是否在主线程,在主线程则放进后台线程的消息队列,否则直接调用
  • ASYNC直接放进后台线程的队列

EventBus内有三个Handler

  • mainThreadPoster持有UI线程的消息循环,发消息给UI线程并在上面处理
  • backgroundPosterasyncPoster都是在EventBus内的线程池上执行

invokeSubscriber就是调用订阅方法

发送粘性事件

1
2
3
4
5
6
7
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);
}

很简单,把事件设置到粘性事件列表的对应项,然后正常发送

事件继承

postSingleEvent()中出现的代码段

1
2
3
4
5
6
7
8
9
10
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}

如果支持事件继承的话,需要查找对应事件类的所有EventType,包括接口类和基类,然后全部发送一遍(可能某些场景有用)

注解处理器

参见生成的类,就是收集所有订阅者的处理函数并结构化形成索引

分享