EventBus 核心要點解析
關注EventBus核心要點
###使用流程
register(object)
eventBus.post(event)
舉個簡單例子
基類Activity
public class CommonActivity extends AppCompatActivity {
protected EMEventBus eventBus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
eventBus = EMEventBus.getDefault();
eventBus.register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
eventBus.unregister(this);
}
//EventBus至少有一個onEvent方法
public void onEvent(String defaultEvent) {
}
}
登錄頁面只訂閱一個LoginEvent
public class LoginActivity extends CommonActivity {
public void onEventMainThread(LoginEvent event) {
}
}
當在另一個頁面 eventBus.post(new LoginEvent()) 的時候發生了什么
- #register
當LoginActivity頁面register的時候
subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
先找出所有LoginActivity中訂閱的Event
{
LoginActivity.onEventMainThread(me.ele.crowdsource.event.LoginEvent)
CommonActivity.onEvent(java.lang.String)
}
然后for循環生成EventBus中第一重要的映射
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
為什么說這個映射重要,因為他對應著一個Event->List訂閱這個Event的對象,有了這個映射,當你要post(event)的時候,就可以輕松找到所有響應的訂閱者。注意,這個list的類型是 CopyOnWriteArrayList
CopyOnWriteArrayList是ArrayList 的一個線程安全的變體,其中所有可變操作(add、set等等)都是通過對底層數組進行一次新的復制來實現的。
CopyOnWriteArrayList適合使用在讀操作遠遠大于寫操作的場景里,比如緩存。發生修改時候做copy,新老版本分離,保證讀的高性能,適用于以讀為主的情況。
接著生成了EventBus中第二重要的映射
private final Map
這里EventBus代碼中一個奇怪的習慣,就是把一個list先添加到map,然后再給這個list add數據,這個思路有異常人,比如下面這樣
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<Class<?>>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
生成了subscriptionsByEventType和typesBySubscriber 這倆個map,register的任務就結束了。
- #post
試想如果你來實現EventBus,當Post一個event的時候,怎么搞?很簡單是根據subscriptionsByEventType找到所有訂閱的方法,然后執行。再看EventBus如何實現的,哈哈,也就是這個思路。只是多了個查找Event所有的父類和實現的接口過程.
/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
synchronized (eventTypesCache) {
List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) {
eventTypes = new ArrayList<Class<?>>();
Class<?> clazz = eventClass;
while (clazz != null) {
eventTypes.add(clazz);
addInterfaces(eventTypes, clazz.getInterfaces());
clazz = clazz.getSuperclass();
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}
尋找訂閱的核心實現在這里
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) {
···
postToSubscription(subscription, event, postingState.isMainThread);
···
}
return false;
}
當真正去響應各個訂閱方法的時候,出現了EventBus的核心實現-用
private final HandlerPoster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
三個隊列來處理所有的方法。關于這里隊列的介紹之前 總結過一次
有興趣的可以看這里,類似的實現handle中也有,自己的項目中也可以用。
postToSubscription 函數中會判斷訂閱者的 ThreadMode,從而決定在什么 Mode 下執行事件響應函數。ThreadMode 共有四類:
- PostThread:默認的 ThreadMode,表示在執行 Post 操作的線程直接調用訂閱者的事件響應方法,不論該線程是否為主線程(UI 線程)。當該線程為主線程時,響應方法中不能有耗時操作,否則有卡主線程的風險。適用場景:對于是否在主線程執行無要求,但若 Post 線程為主線程,不能耗時的操作;
- MainThread:在主線程中執行響應方法。如果發布線程就是主線程,則直接調用訂閱者的事件響應方法,否則通過主線程的 Handler 發送消息在主線程中處理——調用訂閱者的事件響應函數。顯然,MainThread類的方法也不能有耗時操作,以避免卡主線程。適用場景:必須在主線程執行的操作;
- BackgroundThread:在后臺線程中執行響應方法。如果發布線程不是主線程,則直接調用訂閱者的事件響應函數,否則啟動唯一的后臺線程去處理。由于后臺線程是唯一的,當事件超過一個的時候,它們會被放在隊列中依次執行,因此該類響應方法雖然沒有PostThread類和MainThread類方法對性能敏感,但最好不要有重度耗時的操作或太頻繁的輕度耗時操作,以造成其他操作等待。適用場景:操作輕微耗時且不會過于頻繁,即一般的耗時操作都可以放在這里;
- Async:不論發布線程是否為主線程,都使用一個空閑線程來處理。和BackgroundThread不同的是,Async類的所有線程是相互獨立的,因此不會出現卡線程的問題。適用場景:長耗時操作,例如網絡訪問。
post方法中關于線程的處理也耐人尋味,尤其是這個currentPostingThreadState
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
每次post可能在不同的線程,這個時候使用ThreadLocal使得各線程能夠保持各自獨立的一個對象,從源碼可以看出
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
這說明ThreadLocal確實只有一個變量,但是它內部包含一個map,針對每個thread保留一個entry,如果對應的thread不存在則會調用initialValue。
再回到EventBus
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
每個線程維護一個eventQueue,每次post就循環的去發送
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
- #unregister
unregister思路很簡單,就是清除typesBySubscriber里面這個Activity的記錄和subscriptionsByEventType中對應的記錄,但是這里有個刪除list中元素的技巧很有趣。因為之前兆軒剛在上面踩過坑,看EventBus如何刪除subscriptionsByEventType中一個event對應的訂閱list中某個記錄。
private void unubscribeByEventType(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--;
}
}
}
}
重點就是i–和size–,當命中的時候通過–讓游標回退一位,size也減一,這樣就可以讓剩下的元素也正常循環完成。
到此為止,EventBus我認為比較有意思的地方就都列出來了,還是學到了蠻多的東西。
來自: http://xujinyang.github.io/2016/05/24/EventBus-核心要點解析/