一、簡單使用
依賴
implementation 'org.greenrobot:eventbus:3.2.0'
Demo
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注冊訂閱者
EventBus.getDefault().register(this);
// 測試按鈕
findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 事件發布者(Publisher):用于通知 Subscriber 有事件發生??梢栽谌我饩€程任 意位置發送事件。
EventBus.getDefault().post(new ToastEvent("123"));
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 必須要回收 否則會內存泄漏
EventBus.getDefault().unregister(this);
}
/**
* 事件訂閱者(Subscriber):接收特定的事件。
*
* @param event 傳遞的事件
*/
@Subscribe
public void receiveToast(ToastEvent event) {
Toast.makeText(MainActivity.this, "測試點擊事件 --> " + event.getMsg(), Toast.LENGTH_SHORT).show();
}
/**
* 事件(Event):可以是任意類型的對象。通過事件的發布者將事件進行傳遞。
*/
public static class ToastEvent {
private String msg;
public ToastEvent(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
}
二、EventBus簡介
EventBus是一個Android端優化的publish/subscribe消息總線,簡化了應用程序內各組件間、組件與后臺線程間的通信。
作為一個消息總線主要有三個組成部分:
- 事件(Event):可以是任意類型的對象。通過事件的發布者將事件進行傳遞。
- 事件訂閱者(Subscriber):接收特定的事件。
- 事件發布者(Publisher):用于通知 Subscriber 有事件發生。可以在任意線程任 意位置發送事件。
上圖解釋了整個EventBus的大概工作流程:事件的發布者(Publisher)將事件 (Event)通過post()方法發送。EventBus內部進行處理,找到訂閱了該事件 (Event)的事件訂閱者(Subscriber)。然后該事件的訂閱者(Subscriber)通過 onEvent()方法接收事件進行相關處理(關于onEvent()在EventBus 3.0中有改動, 下面詳細說明)。
三、EventBus的進階使用
1.線程模式ThreadMode
當你接收的的事件后,如果處于非UI線程,你要更新UI怎么辦?如果處于UI線程,
你要進行耗時操作,怎么辦?等等其他情況,通過ThreadMode統統幫你解決。
@Subscribe(threadMode = ThreadMode.MainThread)
public void onNewsEvent(NewsEvent event) {
String message = event.getMessage();
mTv_message.setText(message);
}
PostThread:
事件的處理在和事件的發送在相同的進程,所以事件處理時間不應 太長,不然影響事件的發送線程。
MainThread:
事件的處理會在UI線程中執行。事件處理時間不能太長,這個不用說 的,長了會ANR的。
BackgroundThread:
如果事件是在UI線程中發布出來的,那么事件處理就會在子 線程中運行,如果事件本來就是子線程中發布出來的,那么事件處理直接在該子線 程中執行。所有待處理事件會被加到一個隊列中,由對應線程依次處理這些事件, 如果某個事件處理時間太長,會阻塞后面的事件的派發或處理。
Async:
事件處理會在單獨的線程中執行,主要用于在后臺線程中執行耗時操作, 每個事件會開啟一個線程。
2.priority事件優先級
事件的優先級類似廣播的優先級,優先級越高優先獲得消息。 用法展示:
@Subscribe(priority = 100,threadMode = ThreadMode.MAIN)
public void onToastEvent(ToastEvent event) {
Toast.makeText(MainActivity.this, event.getMsg(), Toast.LENGTH_SHORT).show();
}
當多個訂閱者(Subscriber)對同一種事件類型進行訂閱時,即對應的事件處理方 法中接收的事件類型一致,則優先級高(priority 設置的值越大),則會先接收事 件進行處理;優先級低(priority 設置的值越小),則會后接收事件進行處理。
除此之外,EventBus也可以終止對事件繼續傳遞的功能。 用法展示:
@Subscribe(priority = 100, threadMode = ThreadMode.MAIN)
public void onToastEvent(ToastEvent event) {
Toast.makeText(MainActivity.this, event.getMsg(), Toast.LENGTH_SHORT).show();
// 取消后續的事件分發
EventBus.getDefault().cancelEventDelivery(event);
}
這樣其他優先級比100低,并且訂閱了該事件的訂閱者就會接收不到該事件。
3.EventBus黏性事件
EventBus除了普通事件也支持粘性事件。可以理解成:訂閱在發布事件之后,但同 樣可以收到事件。訂閱/解除訂閱和普通事件一樣,但是處理訂閱的方法有所不同, 需要注解中添加sticky = true。 用法展示:
@Subscribe(priority = 100, threadMode = ThreadMode.MAIN, sticky = true)
public void onToastEvent(ToastEvent event) {
Toast.makeText(MainActivity.this, event.getMsg(), Toast.LENGTH_SHORT).show();
// 取消后續的事件分發
EventBus.getDefault().cancelEventDelivery(event);
}
這樣,假設一個ToastEvent 的事件已經發布,此時還沒有注冊訂閱。當設置了 sticky = true,在ToastEvent 的事件發布后,進行注冊。依然能夠接收到之前發布 的事件。
不過這個時候,發布事件的方式就改變了。
EventBus.getDefault().postSticky(new ToastEvent("Toast,發個提示, 祝大家新年快樂!"));
我們如果不再需要該粘性事件我們可以移除
EventBus.getDefault().removeStickyEvent(ToastEvent.class);
或者調用移除所有粘性事件
EventBus.getDefault().removeAllStickyEvents();
四、源碼解析
getDefault()
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}
通過上述代碼可以得知,getDefault()中通過雙檢查鎖(DCL)機制實現了 EventBus的單例機制,獲得了一個默認配置的EventBus對象。 下面我們繼續看 register()方法。
register()
在了解register()之前,我們先要了解一下EventBus中的幾個關鍵的成員變量。方便 對下面內容的理解。
/** Map<訂閱事件, 訂閱該事件的訂閱者集合> */
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
/** Map<訂閱者, 訂閱事件集合> */
private final Map<Object, List<Class<?>>> typesBySubscriber;
/** Map<訂閱事件類類型,訂閱事件實例對象>. */
private final Map<Class<?>, Object> stickyEvents;
下面看具體的register()中執行的代碼。
public void register(Object subscriber) {
//訂閱者類型
Class<?> subscriberClass = subscriber.getClass();
//獲取訂閱者全部的響應函數信息(即上面的onNewsEvent()之類的方法)
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//循環每一個事件響應函數,執行 subscribe()方法,更新訂閱相關信息
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
由此可見,register()第一步獲取訂閱者的類類型. 第二步,通過 SubscriberMethodFinder類來解析訂閱者類,獲取所有的響應函數集合. 第三步,遍歷 訂閱函數,執行 subscribe()方法,更新訂閱相關信息。 關于 subscriberMethodFinder這里就不介紹了。先跟著線索,繼續看subscribe()方法。 subscribe 函數分三步。
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 第一步
// 獲取訂閱的事件類型
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
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();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 第二步
//根據訂閱者,獲得該訂閱者訂閱的事件類型集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
//如果事件類型集合為空,創建新的集合,并加入新訂閱的事件類型。
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//如果事件類型集合不為空,加入新訂閱的事件類型
subscribedEvents.add(eventType);
// 第三步
//該事件是stick=true。
if (subscriberMethod.sticky) {
//響應訂閱事件的父類事件
if (eventInheritance) {
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
//循環獲得每個stickyEvent事件
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);
}
}
}
- 由此可見,第一步:通過subscriptionsByEventType得到該事件類型所有訂閱者信 息隊列,根據優先級將當前訂閱者信息插入到訂閱者隊列 subscriptionsByEventType中;
- 第二步:在typesBySubscriber中得到當前訂閱者訂閱的所有事件隊列,將此事件保 存到隊列typesBySubscriber中,用于后續取消訂閱;
- 第三步:檢查這個事件是否 是 Sticky 事件,如果是則從stickyEvents事件保存隊列中取出該事件類型最后一個 事件發送給當前訂閱者。
到此,便完成了訂閱功能。下面是訂閱的具體流程圖:
unregister()
public synchronized void unregister(Object subscriber) {
// 獲取該訂閱者所有的訂閱事件類類型集合.
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
// 從typesBySubscriber刪除該<訂閱者對象,訂閱事件類類型集合>
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
// 獲取訂閱事件對應的訂閱者信息集合.
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
// 從訂閱者集合中刪除特定的訂閱者.
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
unregister()方法比較簡單,主要完成了subscriptionsByEventType以及
typesBySubscriber兩個集合的同步。
post()
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
//將當前事件添加到其事件隊列
eventQueue.add(event);
//判斷新加入的事件是否在分發中
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
// 循環處理當前線程eventQueue中的每一個event對象.
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
// 處理完知乎重置postingState一些標識信息.
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
post 函數會首先得到當前線程的 post 信息PostingThreadState,其中包含事件隊 列,將當前事件添加到其事件隊列中,然后循環調用 postSingleEvent 函數發布隊 列中的每個事件。
總結
1、要理解EventBus就要從register,unRegister,post,postSticky方法入手。要理解register實質上是將訂閱對象(比如activity)中的每個帶有subscriber的方法找出來,最后獲得調用的就是這些方法。訂閱對象(比如activity)是一組event方法的持有者。
2、后注冊的對象中sticky方法能夠收到之前的stickyEvent方法的原因是EventBus中維護了stickyEvent的hashMap表,在subsribe注冊的時候就遍歷其中有沒有注冊監聽stickyEvent如果有就會執行一次回調。
EventBus缺點
1、使用的時候有定義很多event類
2、event在注冊的時候會調用反射去遍歷注冊對象的方法在其中找出帶有@subscriber標簽的方法,性能不高。
3、需要自己注冊和反注冊,如果忘了反注冊就會導致內存泄漏