事件总线源码分析
基本概念
在安卓中处理不同组件之间的事件传递依靠广播机制,即Intent/BroadcastReceiver机制
,其原理类似于传感网中的Ad hoc网络模式,所有组件处在一种无序状态;
事件总线机制则引入中心控制节点来集中管理事件,类似于移动通信网络中的基站功能。
总线这个概念来自于计算机,计算机中各种功能部件如CPU,显卡之类不会采用两两互联的方式,那样布线会非常复杂,实际是使用总线作为公共通信干线,挂载上所有的功能部件。
事件总线框架采用订阅/发布模型
在这个模型中包含以下元素
1.事件类,这是要传递的事件对象。
2.事件总线,是中心控制节点,负责管理事件的注册,接收发布的事件,并完成事件匹配以进行事件处理。
3.发布者,负责分发事件。
4.订阅者,负责事件处理并注册到事件总线。
事件总线模型比广播机制的优势在于
1.简化了组件之间的通信
2.实现了事件分发和事件处理的解耦,因此二者可以在不同线程中实现。
3.扩展了事件类,实际上事件类就是根类Object
,而不必藏身在Intent
中了。
事件总线模型不能完全替代广播机制,因为广播机制可以完成跨App间组件调用。
EventBus
EventBus
采用发布/订阅模式,想要接收事件必须先订阅总线,这与手机要注册基站类似。EventBus
的作用在于优化Activities, Fragments等之间的通信,并处理能够线程问题。
EventBus
的使用非常简单,包括以下步骤
1.总线的建立。
EventBus eventBus = EventBus.getDefault();
毫无疑问,事件总线对象应该是单例实现,如果要对其进行初始化配置最好放在Applicaiton
类中进行。
2.创建事件,事件就是普通的Java
类,如
public class MessageEvent { public final String message; public MessageEvent(String message) { this.message = message; } }
3.创建订阅方法。订阅者实际上是处理事件的方法,以onEvent
来命名。
public void onEvent(MessageEvent event){ Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show(); }
4.订阅者注册和取消。将包含订阅方法的类注册到总线。
protected void onCreate(Bundle savedInstanceState) { //。。。 EventBus.getDefault().register(this,1); } @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().register(this); }
5.事件发布。
EventBus.getDefault().post(new MessageEvent("MessageEvent"));
线程切换
关于线程切换要注意在事件分发是处在当前线程中的,是比较容易控制的。EventBus
主要简化的是事件处理所处的线程。共有四种线程切换方法,其区别在于事件处理方法的命名。
-
onEvent
方法,为默认PostThread
模式。处理线程就是分发线程。 -
onEventMainThread
方法,主线程模式。处理线程最终为UI线程,与分发线程无关。 -
onEventBackgroundThread
方法,背景线程模式。处理线程最终为背景线程。这意味着如果分发线程是UI线程,则将新建一背景线程作处理线程;如果分发线程不是UI线程,则分发线程就用作处理线程。 -
onEventAsync
方法,异步模式。不管分发线程如何,处理线程都将新建一线程,底层采用线程池技术实现。
Otto
Otto
是Square
推出的事件总线框架,基于Guava
框架。Guava
框架查找订阅方法采用遍历类方法,Otto
则是使用注解来查找,虽然EventBus
在初始版本中也采用注解订阅方法,但因为性能问题改为按照方法名查找。
Otto
的使用与EventBus
类似,最大区别在于是否对订阅方法使用注解。
Bus bus = new Bus(ThreadEnforcer.MAIN); @Subscribe public void answerAvailable(AnswerAvailableEvent event){ // TODO: React to the event somehow! } bus.register(this); bus.post(new AnswerAvailableEvent(42));
两种框架的对比
可以看到在功能和性能上EventBus都完胜Otto。
EventBus源码解析
首先要理解几个概念,先看看订阅方法
void onEvent(MessageEvent event)
订阅方法中包含一个具体事件类作参数,并通过重载实现不同的订阅方法。
-
SubscriberMethod
类表示单个订阅方法,主要域包括
Method method;//订阅方法的反射表示,实现ding'y方法的 ThreadMode threadMode;//处理事件所用线程模型 Class<?> eventType;//具体事件类类型
-
Subscription
类也表示订阅方法,是SubscriberMethod
类的进一步封装,主要域包括
Object subscriber; //具体事件类对象 SubscriberMethod subscriberMethod; int priority; //订阅方法优先级
总的说来,事件总线的原理是在总线中维持两个集合:一个表示订阅方法集合,一个表示事件类型集合。
注册订阅方法分为两步:首先查找所有出所有订阅方法;其次将订阅方法及其事件类型分别加入总线的两个集合中。
事件分发时,需要根据事件对象提取出事件类型,而后构建订阅方法,在总线两个集合中分别查找是否存在该事件;如果存在就按照线程模型分别执行。
-
PostingThreadState
List<Object> eventQueue = new ArrayList<Object>(); boolean isPosting; boolean isMainThread; Subscription subscription;//订阅类 Object event; //事件类型 boolean canceled;
1.EventBus
创建使用getDefault()
方法采用单例模式,也可以通过buidler
指定,需要同步创建。
2.订阅方法注册
EventBus.getDefault().register(this);
总的说来方法订阅包括两步:
这里注意形式上注册到总线的是MainActivity
对象,并不是具体订阅方法,所以存在一个查找出活动对象中所有订阅方法的过程。
同时总线中要维持两个集合:订阅类集合和事件类型集合。新的订阅方法要添加到这两个集合中。
private synchronized void register(Object subscriber, boolean sticky, int priority) { //1.首先对活动类查找出其包含的订阅方法(SubscriberMethod)集合 List<SubscriberMethod> subscriberMethods = findSubscriberMethods(subscriber.getClass()); //2.而后将对每一个订阅方法(SubscriberMethod)注册 for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod, sticky, priority); } }
注册订阅方法(SubscriberMethod)简化版如下,不考虑striky
情况,完成将一个订阅方法A插入总线集合中:
//Object subscriber 如 MainActivity //SubscriberMethod 订阅方法,如onEvent private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) { //获取订阅方法A中的事件类型A.eventType,如MessageEvent Class<?> eventType = subscriberMethod.eventType; //根据事件类型A.eventType获取总线中已经存在的订阅方法(Subscription)集合S CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //根据订阅方法A创建其对应的封装订阅方法B Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority); //在订阅方法集合S中查找封装订阅方法B //首先要处理S=NULL的情况,此时要创建S,并将B插入S。 //如果S已经包含B,抛出异常,即不能重复注册同一个订阅方法。 if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<Subscription>(); subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } //如果S不为空且B不在S中,要将B按照优先级顺序插入到S int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || newSubscription.priority > subscriptions.get(i).priority) { subscriptions.add(i, newSubscription); break; } } //总线中维护一个事件类型集合,还需要将新事件类型A.eventType加入该集合 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<Class<?>>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); }
3.事件分发方法简化版为
public void post(Object event) { //分发事件时将具体事件入队 PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); //处理队列中优先级最高的事件 while (!eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); } }
//Object event 事件类对象 private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { //获取具体事件类对象event对应的事件类型E,如MessageEvent Class<?> eventClass = event.getClass(); //在事件总线集合中查找是否存在具体事件类型E,如果不存在,则分发NoSubscriberEvent事件;如果存在,继续分发。 boolean subscriptionFound = false; subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); }
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { //根据事件类型同步获取总线中的封装订阅方法集合S,这里要注意某个具体事件类型可能有多个线程版本 CopyOnWriteArrayList<Subscription> subscriptions; //遍历S中订阅方法,进行事件处理 if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postToSubscription(subscription, event, postingState.isMainThread); } return true; } return false; }
根据ThreadMode类型处理事件
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case PostThread: invokeSubscriber(subscription, event); break; case MainThread: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case BackgroundThread: 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); } }
4.几种线程有关的事件处理方法
PostThread
方式采用反射完成事件处理。
void invokeSubscriber(Subscription subscription, Object event) { subscription.subscriberMethod.method.invoke(subscription.subscriber, event); }
MainThread
模式在异步情况下采用Handler
完成事件处理,具体类为HandlerPoster
类,这个类采用Looper.getMainLooper()
构造以保证事件处理执行在主线程中。
mainThreadPoster.enqueue(subscription, event);
void enqueue(Subscription subscription, Object event) { //构造一个PendingPost类并将其入队 PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); }
BackgroundThread与Async模式采用线程池来执行,
eventBus.getExecutorService() = Executors.newCachedThreadPool();
public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); eventBus.getExecutorService().execute(this); }
参考文献
greenrobot-EventBus-HOWTO.md
EventBus for Android™
Otto
EventBus Comparison with Square's Otto
eventbus-for-android