作者:郭孝星
校對:郭孝星
關於專案
Android Open Framework analysis專案主要用來分析Android平臺主流開源框架的原始碼與原理實現。
文章目錄
- 一 註冊訂閱者
- 二 釋出事件Event
- 三 接收事件Event
- 四 取消註冊訂閱者
EventBus是一個Android/Java平臺基於訂閱與釋出的通訊框架,可以用於Activities, Fragments, Threads, Services等元件的通訊,也可以用於多執行緒通訊。
EventBus在應用裡的應用是十分廣泛的,那麼除了EventBus這種應用通訊方式外,還有哪些手段呢??
- BroadcastReceiver/LocalBroadcastReceiver:跨域廣播和局域廣播,跨域廣播可以用來做跨程式通訊。局域廣播也是基於Handler實現,可以用來在應用內通訊。
- Handler:這個方式的弊端在於通訊訊息難以管理。
- 介面回撥:介面回撥的好處是比較清晰明顯,但是如果涉及到大量頁面的跳轉或者通訊場景比較複雜,這種方式就變得難以維護,耦合較高。
相當於這些方式EventBus的優點在於使用簡單,事件的訂閱者和釋出者解耦,但是它也有有自己的問題,例如大量Event類的管理,這個我們後續會說。
Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality.
- 官方網站:https://github.com/greenrobot/EventBus
- 原始碼版本:3.1.1
我們先來看一下EventBus的原始碼結構,如下所示:
主要包含了兩個部分:
- eventbus:核心庫。
- eventbus-annotation-processor:註解處理部分。
我們先來一個簡單的Demo,從Demo入手分析事件的訂閱和釋出流程。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_post_event).setOnClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
// 訂閱事件
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
super.onStop();
// 取消訂閱s事件
EventBus.getDefault().unregister(this);
}
// 接收事件Event
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(Event event) {
Toast.makeText(this, event.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_post_event:
// 釋出事件Event
EventBus.getDefault().post(new Event("Event Message"));
break;
}
}
}
複製程式碼
整體的流程還是比較簡單的,如下所示:
- 註冊訂閱者。
- 釋出事件Event。
- 接收事件Event。
- 取消註冊訂閱者。
我們具體來看一下。
一 註冊訂閱者
訂閱事件是通過以下方法來完成的:
EventBus.getDefault().register(this);
複製程式碼
getDefault()用來獲取EventBus例項,當然你也可以通過EventBusBuilder自己構建例項。
public class EventBus {
public void register(Object subscriber) {
// 1. 獲取訂閱者的類名。
Class<?> subscriberClass = subscriber.getClass();
// 2. 查詢當前訂閱者的所有響應函式。
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
// 3. 迴圈每個事件響應函式
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
}
複製程式碼
SubscriberMethod用來描述onEvent()這些方法的資訊,包含方法名、執行緒、Class型別、優先順序、是否是粘性事件。
整個函式的呼叫流程所示:
- 獲取訂閱者的類名。
- 查詢當前訂閱者的所有響應函式。
- 迴圈每個事件響應函式
接著呼叫subscribe()進行事件註冊,如下所示:
public class EventBus {
// 訂閱者佇列
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
// 後續準備取消的事件佇列
private final Map<Object, List<Class<?>>> typesBySubscriber;
// 粘性事件佇列
private final Map<Class<?>, Object> stickyEvents;
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 事件型別(xxxEvent)
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 1. 獲取該事件型別的所有訂閱者資訊。
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);
}
}
int size = subscriptions.size();
// 2. 按照事件優先順序將其插入訂閱者列表中。
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 3. 得到當前訂閱者訂閱的所有事件佇列,存放在typesBySubscriber中,用於後續取消事件訂閱。
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// 4. 是否是粘性事件,如果是粘性事件,則從stickyEvents佇列中取出最後一個該型別的事件傳送給訂閱者。
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
}
複製程式碼
Subscription包含了訂閱者subscriber和訂閱函式subscriberMethod兩個資訊。
該方法的呼叫流程如下所示:
- 獲取該事件型別的所有訂閱者資訊。
- 按照事件優先順序將其插入訂閱者列表中。
- 得到當前訂閱者訂閱的所有事件佇列,存放在typesBySubscriber中,用於後續取消事件訂閱。
- 是否是粘性事件,如果是粘性事件,則從stickyEvents佇列中取出最後一個該型別的事件傳送給訂閱者。
二 釋出事件Event
傳送事件Event是通過以下方法完成的,如下所示:
EventBus.getDefault().post(new Event("Event Message"));
複製程式碼
public class EventBus {
public void post(Object event) {
// 1. 獲取當前執行緒的PostingThreadState物件,該物件包含事件佇列,儲存在ThreadLocal中。
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 2. 將當前事件加入到該執行緒的事件佇列中。
eventQueue.add(event);
// 3. 判斷事件是否在分發中。如果沒有則遍歷事件佇列進行實際分發。
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
// 4. 進行事件分發。
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
}
複製程式碼
PostingThreadState用來描述傳送事件的執行緒的相關狀態資訊,包含事件佇列,是否是主執行緒、訂閱者、事件Event等資訊。
- 獲取當前執行緒的PostingThreadState物件,該物件包含事件佇列,儲存在ThreadLocal中。
- 將當前事件加入到該執行緒的事件佇列中。
- 判斷事件是否在分發中。如果沒有則遍歷事件佇列進行實際分發。
- 進行事件分發。
然後呼叫postSingleEvent()進行事件分發。
public class EventBus {
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// 1. 如果事件允許繼承,則查詢該事件型別的所有父類和介面,依次進行迴圈。
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
// 2. 查詢該事件的所有訂閱者。
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
}
複製程式碼
該方法主要做了以下事情:
- 如果事件允許繼承,則查詢該事件型別的所有父類和介面,依次進行迴圈。
- 查詢該事件的所有訂閱者。
然後呼叫postSingleEventForEventType()方法查詢當前事件的所有訂閱者,如下所示:
public class EventBus {
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 1. 獲取當前事件的所有訂閱者。
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
// 2. 遍歷所有訂閱者。
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
// 3. 根據訂閱者所線上程,呼叫事件響應函式onEvent()。
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;
}
}
複製程式碼
該方法主要做了以下事情:
- 獲取當前事件的所有訂閱者。
- 遍歷所有訂閱者。
- 根據訂閱者所線上程,呼叫事件響應函式onEvent()。
呼叫postToSubscription()方法根據訂閱者所線上程,呼叫事件響應函式onEvent(),這便涉及到接收事件Event的處理了,我們接著來看。
三 接收事件Event
public class EventBus {
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(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);
}
}
}
複製程式碼
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(Event event) {
Toast.makeText(this, event.getMessage(), Toast.LENGTH_SHORT).show();
}
複製程式碼
如上所示,onEvent函式上是可以加Subscribe註解了,該註解標明瞭onEvent()函式在哪個執行緒執行。主要有以下幾個執行緒:
- PostThread:預設的 ThreadMode,表示在執行 Post 操作的執行緒直接呼叫訂閱者的事件響應方法,不論該執行緒是否為主執行緒(UI 執行緒)。當該執行緒為主執行緒 時,響應方法中不能有耗時操作,否則有卡主執行緒的風險。適用場景:對於是否在主執行緒執行無要求,但若 Post 執行緒為主執行緒,不能耗時的操作;
- MainThread:在主執行緒中執行響應方法。如果釋出執行緒就是主執行緒,則直接呼叫訂閱者的事件響應方法,否則通過主執行緒的 Handler 傳送訊息在主執行緒中處理— —呼叫訂閱者的事件響應函式。顯然,MainThread類的方法也不能有耗時操作,以避免卡主執行緒。適用場景:必須在主執行緒執行的操作;
- BackgroundThread:在後臺執行緒中執行響應方法。如果釋出執行緒不是主執行緒,則直接呼叫訂閱者的事件響應函式,否則啟動唯一的後臺執行緒去處理。由於後臺執行緒 是唯一的,當事件超過一個的時候,它們會被放在佇列中依次執行,因此該類響應方法雖然沒有PostThread類和MainThread類方法對效能敏感,但最好不要有重度耗 時的操作或太頻繁的輕度耗時操作,以造成其他操作等待。適用場景:操作輕微耗時且不會過於頻繁,即一般的耗時操作都可以放在這裡;
- Async:不論釋出執行緒是否為主執行緒,都使用一個空閒執行緒來處理。和BackgroundThread不同的是,Async類的所有執行緒是相互獨立的,因此不會出現卡執行緒的問 題。適用場景:長耗時操作,例如網路訪問。
這裡我執行緒執行和EventBus的成員變數對應,它們都實現了Runnable與Poster介面,Poster介面定義了事件排隊功能,這些本質上都是個Runnable,放線上程池裡執行,如下所示:
private final Poster mainThreadPoster; private final BackgroundPoster backgroundPoster; private final AsyncPoster asyncPoster; private final SubscriberMethodFinder subscriberMethodFinder; private final ExecutorService executorService;
四 取消註冊訂閱者
取消註冊訂閱者呼叫的是以下方法:
EventBus.getDefault().unregister(this);
複製程式碼
具體如下所示:
public class EventBus {
public synchronized void unregister(Object subscriber) {
// 1. 獲取當前訂閱者訂閱的所有事件型別。
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
// 2. 遍歷事件佇列,解除事件註冊。
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
// 3. 移除事件訂閱者。
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
}
複製程式碼
取消註冊訂閱者的流程也十分簡單,如下所示:
- 獲取當前訂閱者訂閱的所有事件型別。
- 遍歷事件佇列,解除事件註冊。
- 移除事件訂閱者。
當猴呼叫unsubscribeByEventType()移除訂閱者,如下所示:
public class EventBus {
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
// 1. 獲取所有訂閱者資訊。
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
// 2. 遍歷訂閱者
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
// 3. 移除該訂閱物件。
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
}
複製程式碼
以上便是EventBus核心的實現,還是比較簡單的。