本文要解决的问题
markdown
1.EventBus是如何通过订阅的方式实现消息传递的?
2.什么是粘性事件?如何实现的?
3.Eventbus内部线程调度是如何实现的?
1.EventBus是如何通过订阅的方式实现消息传递的?
首先我们先来看一下我们在执行 EventBus.getDefault().register() 的时候做了哪些事情
ini
public void register(Object obj){
// 先去数据源中获取subcribleMethods,如果不存在,则去寻找方法并添加
List<SubscribleMethod> list = cacheMap.get(obj);
if(list == null){
list = findSubscribleMethods(obj);
cacheMap.put(obj,list);
}
}
public List<SubscribleMethod> findSubscribleMethods(Object obj){
List<SubscribleMethod> list = new ArrayList<>();
Class<?> clazz = obj.getClass();
// 循环去查找父类是否存在subscrible注解方法
while (clazz != null){
// 判断当前是否是系统类,如果是,就退出循环
String name = clazz.getName();
if(name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")){
break;
}
// 得到所有的方法,这个clazz在本项目中,暂时指代的是MainActivity
Method[] methods = clazz.getMethods();
for(Method method : methods){
// 通过注解找到我们需要注册的方法
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if(subscribe == null){
continue;
}
// 获取方法中的参数,并判断
Class<?>[] types = method.getParameterTypes();
if(types.length != 1){
throw new RuntimeException("EventBus只能接受一个参数");
}
// 获取线程模式
ThreadMode threadMode = subscribe.threadMode();
SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,types[0]);
list.add(subscribleMethod);
}
clazz = clazz.getSuperclass();
}
return list;
}
通过上面代码我们可以知道EventBus在register的时候是讲注册类的所有订阅方法subscribleMethod都添加到了cacheMap里面,通过Subscribe注解我们可以获取订阅方法,方法参数和线程模式。接下来看一下 EventBus.getDefault().post(bean)都做了哪些事情。
typescript
public void post(final Object type){
Set<Object> set = cacheMap.keySet();
Iterator<Object> iterator = set.iterator();
while (iterator.hasNext()){
final Object obj = iterator.next();
List<SubscribleMethod> list = cacheMap.get(obj);
for(final SubscribleMethod subscribleMethod : list){
// 简单的理解:两个列对比一下,看看是否一致 (不严谨的说法)
// a(subscribleMethod.getType())对象所对应的类信息,是b(type.getClass())对象所对应的类信息的父类或者父接口
if(subscribleMethod.getType().isAssignableFrom(type.getClass())){
switch (subscribleMethod.getThreadMode()){
// 不管你在post是在主线程 还是在子线程,我都在主线程接受
case MAIN:
// 主 - 主
if(Looper.myLooper() == Looper.getMainLooper()){
invoke(subscribleMethod,obj,type);
}else{
// 子 - 主
mHandler.post(new Runnable() {
@Override
public void run() {
invoke(subscribleMethod,obj,type);
}
});
}
break;
// 不管你在post是在子线程还是在主线程,我都在子线程接受
case BACKGROUND:
// 主 - 子
if(Looper.myLooper() == Looper.getMainLooper()){
mExecutorService.execute(new Runnable() {
@Override
public void run() {
invoke(subscribleMethod,obj,type);
}
});
}else{
// 子 - 子
invoke(subscribleMethod,obj,type);
}
break;
}
}
}
}
}
通过上面代码我们可以知道post方法就是通过cacheMap集合进行遍历,通过post传递的bean类型从cacheMap集合里面去匹配subscribleMethod里面对应的订阅方法,然后通过method.invoke()反射的方式,最终执行订阅方法。其中涉及到线程切换也是通过handler、looper机制实现。由此我们可以知道,EventBus消息传递机制就是通过注解和反射原理实现事件总线这种解耦的特性。
2.什么是粘性事件?如何实现的?
erlang
粘性事件,是指在发送事件之后再订阅该事件也能收到该事件,这就使得我们可以预先处理一些事件,让有消费者时再把这些事件投递给消费者.
typescript
发送粘性事件
public void sticky(View view) {
EventBus.getDefault().postSticky(new EventBean("abc"));
}
接收粘性事件
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveSoundRecongnizedmsg(EventBean bean) {
L.i(bean.getName());
}
存储粘性事件
csharp
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);
}
通过源码得知当粘性事件发送之后就会被存储到stickyEvents队列里面,当有相对应的订阅的时候才会收到粘性事件。
scss
//当订阅的事件为粘性时 执行以下方法
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);
}
}
当订阅方法,粘性参数为true的时候,会去检索粘性事件存储队列,当队列有匹配的对象的时候执行订阅方法。
vbnet
public boolean removeStickyEvent(Object event) {
synchronized (stickyEvents) {
Class<?> eventType = event.getClass();
Object existingEvent = stickyEvents.get(eventType);
if (event.equals(existingEvent)) {
stickyEvents.remove(eventType);
return true;
} else {
return false;
}
}
}
移除粘性事件(当粘性事件处理过之后,需移除粘性事件,否则这个事件会一直存储在队列里面,每次启动都会执行订阅方法) 我们看到移除粘性事件方法是从stickyEvents队列里面找到匹配的对象,然后移除,此事件就不会被重复执行了。
3.Eventbus内部线程调度是如何实现的?
arduino
public enum ThreadMode {
// 事件的处理和事件的发送在相同的进程
POSTING,
//事件的处理会在UI线程执行
MAIN,
//后台进程,处理如保存到数据库
BACKGROUND
//异步执行,另起线程操作,事件的处理会在单独的线程执行,主要用于后台线程中耗时操作
ASYNC
}
我们知道eventbus的订阅方法有以上四种线程模式,那么event是如何从发送者的线程切换到接收方的线程的呢。
csharp
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);
}
}
由此可以得知eventbus是通过 mainThreadPoster; backgroundPoster; asyncPoster的enqueue方法进行线程调度的。
scala
public class HandlerPoster extends Handler implements Poster {}
final class BackgroundPoster implements Runnable, Poster {}
class AsyncPoster implements Runnable, Poster {}
通过上面代码我们可以得知 mainThreadPoster; backgroundPoster; asyncPoster这三个类分别继承自Handler Runnable Runnable,也就是说EventBus线程切换底层是通过Handle的handleMessage()方法回到主线程,通过Runnable的run方法切到子线程。