事件總線源碼分析

基本概念

在安卓中處理不一樣組件之間的事件傳遞依靠廣播機制,即Intent/BroadcastReceiver機制,其原理相似於傳感網中的Ad hoc網絡模式,全部組件處在一種無序狀態;android

事件總線機制則引入中心控制節點來集中管理事件,相似於移動通訊網絡中的基站功能。git

總線這個概念來自於計算機,計算機中各類功能部件如CPU,顯卡之類不會採用兩兩互聯的方式,那樣佈線會很是複雜,實際是使用總線做爲公共通訊幹線,掛載上全部的功能部件。github

事件總線框架採用訂閱/發佈模型
事件總線的模型網絡

在這個模型中包含如下元素框架

1.事件類,這是要傳遞的事件對象。
2.事件總線,是中心控制節點,負責管理事件的註冊,接收發布的事件,並完成事件匹配以進行事件處理。
3.發佈者,負責分發事件。
4.訂閱者,負責事件處理並註冊到事件總線。異步

事件總線模型比廣播機制的優點在於async

1.簡化了組件之間的通訊
2.實現了事件分發和事件處理的解耦,所以兩者能夠在不一樣線程中實現。
3.擴展了事件類,實際上事件類就是根類Object,而沒必要藏身在Intent中了。ide

事件總線模型不能徹底替代廣播機制,由於廣播機制能夠完成跨App間組件調用。oop

EventBus

EventBus採用發佈/訂閱模式,想要接收事件必須先訂閱總線,這與手機要註冊基站相似。EventBus的做用在於優化Activities, Fragments等之間的通訊,並處理可以線程問題。post

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

OttoSquare推出的事件總線框架,基於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);
}

BackgroundThreadAsync模式採用線程池來執行,

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

相關文章
相關標籤/搜索