提到EventBus,相信大多数安卓开发者都用过,它是一个为Android 设计的一个基于观察者模式的发布/订阅事件总线框架,将事件的接收者和发送者分开,简化了组件之间的通信操作,使用非常简单,源码实现只有一两千行,也很适合学习。
EventBus内部利用handler机制实现了跨线程通信,因此不需要我们自己利用handler机制在线程间切换数据(注意一点,EventBus不能实现跨进程通信,因为EventBus的单例仅在一个进程内有效)。
粘性事件是EventBus里面一个比较有特色的功能,什么是粘性事件呢?要想解答这个问题,我们要从普通事件说起。
public class EventBusTestActivity extends AppCompatActivity {
private ActivityEventBusTestBinding bind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bind=ActivityEventBusTestBinding.inflate(getLayoutInflater());
setContentView(bind.getRoot());
//注册总线
EventBus.getDefault().register(this);
bind.btnSendMsg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//发送数据
EventBus.getDefault().post(new MyMessage("普通消息"));
}
});
}
class MyMessage{
final String msg;
public MyMessage(String msg) {
this.msg = msg;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消总线注册
EventBus.getDefault().unregister(this);
}
}
总体上,一个Activity的普通EventBus的事件的完整周期是这样的:
注册到总线 -> 发送消息 ->销毁消息
看上去很完美,但是,假如有两个activity呢?ActivityA发送消息,ActivityB也想接收到,但是ActivityA发送消息时,ActivityB尚未创建,那就很尴尬了,粘性事件就是为了解决这样的问题的。
使用粘性事件,可以在订阅的activity创建之前发出,在创建完成后再接收。
ActivityA创建,注册总线->点击按钮,发送事件->ActivityB创建,消费事件
具体是怎么实现的呢?首先我们看一下粘性事件的发送函数
private final Map<Class<?>, Object> stickyEvents;
...
EventBus(EventBusBuilder builder) {
...
stickyEvents = new ConcurrentHashMap<>();
...
}
...
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);
}
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
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;
}
}
}
可以看到,这里仅仅是将粘性事件放到了一个ConcurrentHashMap中保存,然后又调用了post来发送消息,可以保证消息的正常发送。但是在这里我们并没有看到eventbus是如何在目标activity创建之后,发送消息的。其实奥秘在activity注册EventBus的那部分:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
...
if (subscriberMethod.sticky) {
if (eventInheritance) {
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, isMainThread());
}
}
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 MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(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);
}
}
看了这段代码估计大家就恍然大悟了,原来,在注册时,检测到存在粘性事件(具体存放位置是粘性事件发布时,加入的粘性事件池)时,就直接对消息进行发布了,这就是为什么可以在activity创建前发布粘性事件,当创建时可以接收到消息的原理。
register->subscribe->checkPostStickyEventToSubscription->postToSubscription
你可能还想问,当发送粘性事件时,目标activity已经创建了,那会发生什么呢?会收到两次重复的消息吗?答案显然是否定的,因为在目标activity已经创建后,不再执行register函数,自然也没法出现两条重复的信息,假如你不小心写了两次EventBus.getDefault().register(this),也没关系,因为EventBus在类注册后,就不再重复注册了。
上面的结论都可以看着EventBus的源码捋一捋。