事件總線源碼分析

jopen 8年前發布 | 7K 次閱讀 Android開發 移動開發

基本概念

在安卓中處理不同組件之間的事件傳遞依靠廣播機制,即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

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

來自: http://segmentfault.com/a/1190000004312745

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!