事件總線源碼分析
基本概念
在安卓中處理不同組件之間的事件傳遞依靠廣播機制,即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