EventBus的使用和原始碼解析

robert_chao發表於2016-06-01

一、基本介紹

EventBus 是一個 Android 事件釋出/訂閱框架,通過解耦釋出者和訂閱者簡化 Android 事件傳遞,這裡的事件可以理解為訊息,本文中統一稱為事件。事件傳遞既可用於 Android 四大元件間通訊,也可以使用者非同步執行緒和主執行緒間通訊等等。EventBus

EventBus3.0版本有較大的更新,效能上有很大提升。這裡主要介紹新版本。

傳統的事件傳遞方式包括:Handler、BroadCastReceiver、Interface 回撥,相比之下 EventBus 的優點是程式碼簡潔,使用簡單,並將事件釋出和訂閱充分解耦。類似框架OTTO。

二、EventBus & Otto對比

共同點

1、都是事件匯流排框架,滿足訊息/事件傳遞的同時,也實現了元件間的解耦.
2、註冊的共同點都是採用method方法進行一個整合。

3、都採用註解的方式來標註訂閱方法(舊版本的EventBus通過固定方法名標記訂閱者)

4、大部分規則相同,比如訂閱方法只能有一個引數。

5、都不適用程式間通訊

不同點

1、OTTO更加輕量級,結構簡單。EventBus稍微複雜一些。

2、OTTO預設在主執行緒中使用,不能在其他執行緒使用,通過設定ThreadEnforcer可以在任意執行緒使用,但是訊息傳遞不能指定目標執行緒,EventBus實現了4種ThreadMode,執行緒之間訊息傳遞非常靈活。
3、EventBus支援粘性事件,而OTTO不支援。即先發訊息,再註冊訂閱者仍然能夠收到訊息。

3、OTTO有預設的生產者方法,可以產生預設訊息,EventBus沒有

三、EventBus的簡單使用介紹

定義訊息

public class MessageEvent {
 //定義相關屬性
 }

註冊

eventBus.register(this);

定義訂閱者

@Subscribe
public void onEvent(MessageEvent event) {
//收到訊息後的處理
};

傳送訊息

eventBus.post(event);

解綁

eventBus.unregister(this);

四、原始碼解析

1、EventBus構造

通常我們呼叫EventBus.getDefault()獲取EventBus
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

這個方法是執行緒安全的,EventBus的無參構造方法使用預設構建器DEFAULT_BUILDER構造,EventBusBuilder指定了EventBus的一些行為,用於輸出log,查錯,除錯等。
logSubscriberExceptions 指定了收到SubscriberExceptionEvent型別訊息時丟擲異常的行為,預設為true,列印log,如果為false不輸出log
輸出的資訊有
Log.e(TAG, "SubscriberExceptionEvent subscriber " + subscription.subscriber.getClass()
    + " threw an exception", cause);
    SubscriberExceptionEvent exEvent = (SubscriberExceptionEvent) event;
    Log.e(TAG, "Initial event " + exEvent.causingEvent + " caused exception in "
     + exEvent.causingSubscriber, exEvent.throwable);
Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
      + subscription.subscriber.getClass(), cause);

對logNoSubscriberMessages指定了傳送了沒有訂閱者的訊息的行為,預設為true,列印log,如果為false不輸出log
輸出的資訊為
Log.d(TAG, "No subscribers registered for event " + eventClass);
sendSubscriberExceptionEvent指定是否傳送SubscriberExceptionEvent,預設為true
操作行為
SubscriberExceptionEvent exEvent = 
new SubscriberExceptionEvent(this, cause, event,subscription.subscriber);
post(exEvent);

sendNoSubscriberEvent指定了沒有訂閱者時的行為,預設為true,傳送NoSubscriberEvent這樣一個訊息
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
              eventClass != SubscriberExceptionEvent.class) {
        post(new NoSubscriberEvent(this, event));
}

throwSubscriberException指定了訂閱方法執行異常時是否丟擲異常,預設為false
if (logSubscriberExceptions) {
          Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
                  + subscription.subscriber.getClass(), cause);
}

eventInheritance指定是否發訊息給這個事件的父事件對應的訂閱者,預設為true。如果為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);
       }
   } else {
    subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
ignoreGeneratedIndex指定是否忽略設定索引,這個值預設是false,這段程式碼中使用的兩種解析方式不容易看懂
if (ignoreGeneratedIndex) {
    subscriberMethods = findUsingReflection(subscriberClass);
 } else {
    subscriberMethods = findUsingInfo(subscriberClass);
}
findUsingReflection利用反射來獲取訂閱類中的訂閱方法資訊
findUsingInfo從註解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法資訊
findUsingInfo執行效率更高一些,預設使用這種。
strictMethodVerification指定是否嚴格校驗,預設為false。嚴格校驗會直接丟擲異常
else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException("@Subscribe method " + methodName +
                    "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }

2、註冊與解析

註冊之後,解析出了所有的訂閱方法,方法引數型別
public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

相關對映主要是兩個
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
subscriptionsByEventType的key是訂閱者訊息型別,value是Subscription,是具體訂閱者對應註冊物件和方法
typesBySubscriber的key是註冊物件,value是對應所有訂閱者訊息型別的list
解析器是SubscriberMethodFinder這個類,EventBus中有subscriberMethodFinder這個例項,呼叫
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)這個方法解析方法資訊
如果沒有訂閱者方法,會丟擲異常
throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
如果已經註冊過,會丟擲異常
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
如果訂閱者是粘性事件,如果粘性事件不為空會直接傳送粘性事件。
粘性事件儲存結構為
private final Map<Class<?>, Object> stickyEvents;

stickyEvents的key是事件型別,Object是對應的具體事件。key,value唯一,說明粘性事件是會被覆蓋的。這樣可以節省記憶體開銷。

3、傳送訊息

public void post(Object event) {
       PostingThreadState postingState = currentPostingThreadState.get();
       List<Object> eventQueue = postingState.eventQueue;
       eventQueue.add(event);
       if (!postingState.isPosting) {
           postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
           postingState.isPosting = true;
           if (postingState.canceled) {
               throw new EventBusException("Internal error. Abort state was not reset");
           }
           try {
               while (!eventQueue.isEmpty()) {
                   postSingleEvent(eventQueue.remove(0), postingState);
               }
           } finally {
               postingState.isPosting = false;
               postingState.isMainThread = false;
           }
       }
}

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

post執行緒狀態通過ThreadLocal維護,保證執行緒隔離,不需要加鎖,提高執行效率。
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};


經過以下四個方法,事件分發到對應執行緒的對應訂閱者
private void postSingleEvent(Object event, PostingThreadState postingState) 
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread)
void invokeSubscriber(Subscription subscription, Object event) 
特別注意執行緒處理
switch (subscription.subscriberMethod.threadMode) {
           case POSTING:
               invokeSubscriber(subscription, event);
               break;
           case MAIN:
               if (isMainThread) {
                   invokeSubscriber(subscription, event);
               } else {
                   mainThreadPoster.enqueue(subscription, event);
               }
               break;
           case BACKGROUND:
              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);
       }

POSTING:是在當前執行緒執行
MAIN:拋到主執行緒
BACKGROUND:如果是在主執行緒,拋到新執行緒,如果不是在主執行緒,就在當前執行緒執行
ASYNC:拋到新執行緒中
沒有訂閱者的訊息預設會被重新包裝為NoSubscriberEvent,可以做一些全域性捕獲處理

4、解綁

public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
做一些清理工作,注意不要重複解綁。


剛開始已經說過了,EventBus跟OTTO很像,而 Otto 是基於 Guava 的增強的事件匯流排,Guava 是google一個很強大的java開源庫。EventBus改進是最多的,據說是效率最高的,這個沒有做過相關測試。但是從實現上來看,對效能優化處理還是做了很多事情。


歡迎掃描二維碼,關注公眾賬號








 








相關文章