Android事件傳遞三部曲:事件總線EventBus(上)
常用的事件傳遞方式包括:Handler、BroadcastReceiver、Interface 回調、事件總線EventBus,除去回調這種相對簡單的多的方式我們不討論,Handler的原理已經在之前分析過,接下來要分析的就是EventBus以及BroadcastReceiver,然后最后分析他們各自有優劣以及適用場景。今天的主角就是EventBus
因為整個分析下來,EventBus涉及到的內容還是很多的,所以我將其分成了兩個部分,分作上下篇,其中上篇主要簡單分析事件具體是怎么在EventBus中傳遞的,怎么從發布者的手中到達訂閱者手中,在這個分析中,我們會特意跳過一些比較難的部分,只是快速地了解整個EventBus的架構;而在下篇中,將詳細解釋上篇中跳過的部分,將EventBus中的每個特性解釋清楚。
基本使用
首先還是從最基本的使用場景出發,一步步來跟蹤:
public class Example {
public void onCreate() {
EventBus bus = EventBus.getDefault();
bus.register(this);
bus.post("給xxx的一封信");
}
@Subscribe
public void subscribeEvent(String message) {
Log.i("TAG", "收到:" + message);
}
public void onDestroy() {
EventBus.unregister(this);
}
}
這就是EventBus的最簡單的使用方式了,從獲得一個 EventBus 實例開始,然后添加訂閱,發送消息,然后訂閱方收到消息進行處理,再到最后在合適的時機取消訂閱,主要就是:訂閱,發送消息,接受消息,取消訂閱這四個步驟,再抽象一下就是兩對方法:訂閱和取消訂閱,發送和接受,接下來分析的大致思路也都是按照依次進行,
今天我們就來看下,最主要的就是三行代碼,加一個Subscribe注解的public方法,這個消息是怎么從轉回來的。首先我們從第一行代碼看起:
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
沒錯,就是一個普通的單例,調用的就是默認的構造器,倒是看不出來有什么神奇之處,因為主要的處理都在它的構造器里:
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public EventBus() {
this(DEFAULT_BUILDER);
}
EventBus(EventBusBuilder builder) {
// 以事件類型為鍵,存放所有的由subscriber和subscriberMethod組成的subscription
subscriptionsByEventType = new HashMap<>();
// 已subscriber為鍵,存放subscriber訂閱的所有事件類型
typesBySubscriber = new HashMap<>();
// 存放所有的sticky事件
stickyEvents = new ConcurrentHashMap<>();
// 不同的消息發送器,將根據ThreadMode決定使用哪種,下面會詳細解釋他們各自的功效
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
// 后面還有很多從EventBusBuilder取出的配置參數
...
}
可以看到訂閱方法列表以及訂閱者列表都是 EventBus 的成員變量,所以各個 EventBus 之間的數據是不共享的,所以訂閱者只會收到特定 EventBus 發送的事件,所以 new EventBus() 的適用范圍比較小,因為你new出來的 EventBus 還是要保存起來,不然它沒有任何用處,如果要全局使用的話,還得把它標記成靜態變量,而這件事,EventBus已經幫我們做好了,那就是 EventBus.getDefault() 。當我們需要配置一些EventBus的屬性時,這時候就得借助 EventBusBuilder ,具體能配置什么屬性就不在這里展開,下面分析的時候涉及到我們會提及
有了 EventBus 具體實例之后我們就可以訂閱它,接下來就是 register() :
訂閱
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 找出subscriber中的所有訂閱方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 處理每一個訂閱方法
subscribe(subscriber, subscriberMethod);
}
}
}
在這里我們牽涉到了EventBus中很重要的一環,那就是:找出subscriber的所有訂閱方法,在這里我們先行跳過,可以提前預告一下的是,它可以通過兩種方式獲取:一種是通過反射,一種是通過AnnotationProcessor。繼續向下看就是處理每一個訂閱方法
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
// 創建一個由subscriber和subscriberMethod組成的對象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 找到eventType對應的訂閱列表中,如果沒有則新建一個
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
// 將新建的Subscription對象添加到對應的列表中,添加順序由priority決定,后面會詳細解釋
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription); // 3
break;
}
}
// 保存subscriber訂閱的所有事件類型,方便取消訂閱
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// 省去數行代碼,關于sticky
}
別看這段代碼有點長,其實主要就做了兩件事,更新了兩個最重要的Map: subscriptionsByEventType 和 typesBySubscriber ,已備我們在后面 post 事件時使用。其中,前者保存了已 eventType 為鍵的所有相關的 subscription 。
到這里為止,整個事件的訂閱就結束了,我們拿到了什么?兩個填充了必要數據的HashMap,接下來就是這兩個HashMap發揮作用的時候了,預告一下這兩個HashMap的作用,一個是用于發送事件,一個是用于取消訂閱。整個的流程圖大概就是整個樣子(圖片來自codeKK),最后的Sticky事件不用理會,會在下篇再解釋:
發送事件
// 有關ThreadLocal,大家可以看其他的相關文章,不在這里展開,它是和線程綁定的
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
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 {
// 開始分發,將當前線程event隊列所有對象全部清空并分發出去
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
首先在發送事件所在的線程創建一個 PostingThreadState 對象,這個對象有一個事件隊列,將新的事件添加到隊列中,然后開始分發并將發送過的事件移除隊列,直到將隊列中所有的事件發送完畢。
其實在這里,我有點疑惑,為什么要用到 ThreadLocal 和 eventQueue ,為什么不是直接調用 postSingleEvent() ,表面上看的是防止同一個線程中消息發送堵塞,所以利用 eventQueue 來緩存將要發送的事件,但是在同一個線程中,代碼都是順序執行的,也只有等上一個事件post完成之后,下一個post才會執行,所以在同一個線程中使用隊列并不能達到防治堵塞的目的,歡迎有了解的朋友指教
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// 檢查是否支持事件繼承,如果支持,則會找出當前事件的所有父類以及父類接口,
// 訂閱了它的父類或者父類接口的subscriber也會收到消息,默認是true
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);
}
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 我們在訂閱時填充的訂閱事件列表在這里使用
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
// 發送到目標訂閱者的目標訂閱方法
postToSubscription(subscription, event, postingState.isMainThread);
// 每發送一次,檢查一下事件是否被取消
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
在這里,終于將發送的時間和 subscriber 聯系了起來,根據發送事件的類型找到訂閱了這個事件的訂閱列表,通知列表中每個訂閱者:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
// 省略線程調度相關代碼,會在下面再詳細解釋
invokeSubscriber(subscription, event);
}
void invokeSubscriber(Subscription subscription, Object event) {
// 省略一些代碼
subscription.subscriberMethod.method.invoke(subscription.subscriber, event); // 2
}
終于走到了最后,看到了 subscriber 的 subscriberMethod 最終通過反射的方式被執行,整個事件分發的過程也就到此結束了。整個函數流程圖是這樣的(圖片來自codeKK):
取消訂閱
因為篇幅的原因,取消訂閱的方法我們就不在這里展示,估計大家也能想到到:就是通過 typesBySubscriber 這個HashMap找到訂閱者訂閱的所有事件,然后在 subscriptionsByEventType 的各個事件訂閱列表中將這個訂閱者對應的訂閱事件移除,最后再將訂閱者從 typesBySubscriber 移除。
總結
至此,簡單的EventBus事件流就算解釋清楚了,主要就是兩個方法,一個是 register ,一個是 post ,分別用于注冊保存 subscriber 和消息通知 subscriber , 在注冊的過程中,通過 subscriberMethodFinder 將 subscriber 中所有的訂閱方法都找出來,并根據兩種不同的規則分別按照 eventType 和 subscriber 將訂閱方法緩存在 Map 中,以供后續的步驟使用;在 post 事件中,是以線程為單位的,將事件放入當前線程的消息隊列中,然后依次循環取出發送,直至隊列為空,按照事件的類型找到當前事件類型相關的 Subscription 列表,如果當前bus還支持事件繼承的話,還會找到當前事件類型的父類和父類接口對應的 Subscription 列表,找到這樣的訂閱列表之后,順序取出每一個 Subscription ,然后通過反射調用 subscriber 的訂閱方法,至此,整個事件的傳遞就結束了。分析完整個流程之后,我們在看EventBus的類圖(圖片來自codeKK):
依照這個圖中,再回憶我們分析的整個過程:首先 EventBus 的實例都是依據 EventBusBuilder 創建的,我們可以自定義 EventBusBuilder 來達到定制 EventBus 的目的, SubscriberMethodFinder 是用于找出訂閱者中的所有的訂閱方法,然后訂閱方法和訂閱者組成一個叫做 Subscription 的訂閱事件, EventBus 保存的就是 Subscription 的列表,類圖下半部分我們會在下篇詳細解釋,在這里簡單介紹下,最后執行訂閱者的訂閱方法時,不是簡單的當前線程執行調用反射,而是根據訂閱方法不同的 threadMode 選擇不同的 Poster 將傳遞到目標線程然后在異步線程或者主線程執行
大概就是這些內容,當然我們還省去三個很重要的點沒有說清楚,那就是:SubscriberMethodFinder、ThreadMode、以及StickEvent,會在下篇詳細介紹。
- Android消息處理機制:Handler|Message
- Android事件傳遞三部曲:事件總線EventBus(上)
- Android事件傳遞三部曲:事件總線EventBus(下)
- Android事件傳遞三部曲:本地廣播LocalBroadcastManager
來自:http://shaohui.xyz/2017/01/20/android-messaging-2-eventbus/