EventBus

555 阅读4分钟

简述

我么在进行不同页面切换时,有时需要与其他界面进行通信,原生的广播可以实现,此外用的比较多的时EventBus。其基于观察者模式实现。同时支持在其他线程运行。 今天就分析一下其原理。总体来说比较容易理解。

注册

获取注册类的订阅方法

  • 调用findSubscriberMethods获取注册方法
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
        
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    }

  • findSubscriberMethods提供了两种方法获取
  1. 首先从缓存中获取
  2. 根据ignoreGeneratedIndex去调用不同的方法,一个是反射,一种使用生成的索引
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
        } else {
 subscriberMethods = findUsingInfo(subscriberClass);
        }

  • 注册方法订阅
  1. 遍历索引订阅方法,
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
    subscribe(subscriber, subscriberMethod);
            }
        }
  1. 获取给定事件类型的列表

其利用的HashMap,将每个事件类型作为key,即订阅方法的第一个参数。每个事件对象的订阅者用列表存储

Class<?> eventType = subscriberMethod.eventType;
 Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);       
  1. 按照等级顺序添加到列表中
for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
  1. 同时添加到事件按照订阅类区分的列表
 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

  1. 主线程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");
                }
            }
        }
  1. 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;
}

反思

  1. 由于EventBus松耦合,会导致事件不好管理,不太好确认那里触发了事件,尤其时后面的人重新接受时。
  • 第一种方式就是每次注册事件,写好相应的文档,备注那个事件在那里触发,触发的方法在那里注册的。
  • 可以考虑使用工具自动生成相应的文档,类似ARouter的方式。
  1. Event 跨进程通信 EventBus只支持跨线程,而不支持跨进程,
  • 可以使用IPC做映射表,在两个进程各维护一个EventBus,但是需要自己维护维护register和unregister的关系
  • 使用HermesEventBus(github.com/Xiaofei-it/…)