简述
我么在进行不同页面切换时,有时需要与其他界面进行通信,原生的广播可以实现,此外用的比较多的时EventBus。其基于观察者模式实现。同时支持在其他线程运行。 今天就分析一下其原理。总体来说比较容易理解。
注册
获取注册类的订阅方法
- 调用findSubscriberMethods获取注册方法
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
}
- findSubscriberMethods提供了两种方法获取
- 首先从缓存中获取
- 根据ignoreGeneratedIndex去调用不同的方法,一个是反射,一种使用生成的索引
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
- 注册方法订阅
- 遍历索引订阅方法,
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
- 获取给定事件类型的列表
其利用的HashMap,将每个事件类型作为key,即订阅方法的第一个参数。每个事件对象的订阅者用列表存储
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
- 按照等级顺序添加到列表中
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
- 同时添加到事件按照订阅类区分的列表
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
分发事件
post发送某一事件对象
- 获取当前方法调用所在线程的事件分发状态和事件队列
- 将事件添加到该线程所在的队列
- 其也是利用的ThreadLocal
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");
}
//当没有要发送的了,即遍历刚才添加到eventqueue中的事件,直到空
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
查询所有的事件类型
- 包括父类,接口等
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);
}
}
/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
- 查找该类型事件即(定义的事件对象)所订阅的类的方法
synchronized (this) {
//注册时即添加到该列表
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
}
调用注册事件的方法
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;
- 对于在UI线程的,反射调用注册的方法
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
- 不在ui线程,使用mainThreadPoster切换到ui线程,
- 异步和后台类似,分别使用AsyncPoster,BackgrounPoster
Poster的原理
切换主线程使用Poster,异步使用AsyncPoster,后台使用BackgroundPoster
- 主线程Poster
//创建poster
mainThreadSupport.createPoster
//实际实现为HandlerPoster
@Override
public Poster createPoster(EventBus eventBus) {
return new HandlerPoster(eventBus, looper, 10);
}
//其enqueue就是发送消息到切换到ui线程
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
- AsyncPoster BackgoundPoster跟AsyncPoster原理类型
- 将当前事件切换到非当前线程执行
- 即将其通过asyncPoster加入队列
- 然后使用线程池执行方法
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
- executerservice在创建EventBus时设置
executorService = builder.executorService;
- pendingQueue 通过链表实现,类似生产者-消费模式
final class PendingPostQueue {
private PendingPost head;
private PendingPost tail;
}
inal class PendingPost {
private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
Object event;
Subscription subscription;
PendingPost next;
}
反思
- 由于EventBus松耦合,会导致事件不好管理,不太好确认那里触发了事件,尤其时后面的人重新接受时。
- 第一种方式就是每次注册事件,写好相应的文档,备注那个事件在那里触发,触发的方法在那里注册的。
- 可以考虑使用工具自动生成相应的文档,类似ARouter的方式。
- Event 跨进程通信 EventBus只支持跨线程,而不支持跨进程,
- 可以使用IPC做映射表,在两个进程各维护一个EventBus,但是需要自己维护维护register和unregister的关系
- 使用HermesEventBus(github.com/Xiaofei-it/…)