對於 Eventbus ,相信很多 Android 小伙伴都用到過。
1、創建事件實體類
所謂的事件實體類,就是傳遞的事件,一個組件向另一個組件發送的信息可以儲存在一個類中,該類就是一個事件,會被 EventBus 發送給訂閱者。新建 MessageEvent.java:
public class MessageEvent { private String message; public MessageEvent(String message){ this.message = message; } public String getMessage(){ return message; } }
2、注冊和反注冊
通過以下代碼:
EventBus.getDefault().register(this);
即可將當前類注冊,成為訂閱者,即對應觀察者模式的“觀察者”,一旦有事件發送過來,該觀察者就會接收到匹配的事件。通常,在類的初始化時便進行注冊,如果是 Activity 則在的 onCreate()方法內進行注冊。
當訂閱者不再需要接受事件的時候,我們需要解除注冊,釋放內存:
EventBus.getDefault().unregister(this);
3、添加訂閱方法
回想觀察者模式,觀察者有着一個 update() 方法,在接收到事件的時候會調用該 update() 方法,這個方法就是一個訂閱方法。在EventBus 3.0中,聲明一個訂閱方法需要用到 @Subscribe 注解,因此在訂閱者類中添加一個有着 @Subscribe 注解的方法即可,方法名字可自定義,而且必須是public權限,其方法參數有且只能有一個,另外類型必須為第一步定義好的事件類型(比如上面的 MessageEvent),如下所示:
@Subscribe public void onEvent(AnyEventType event) { /* Do something */ }
完整的 MainActivity.java 文件如下所示:
public class MainActivity extends Activity { private TextView textView; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //注冊成為訂閱者 EventBus.getDefault().register(this); textView = (TextView) findViewById(R.id.tv_text); button = (Button) findViewById(R.id.secondActivityBtn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); } }); } //訂閱方法,當接收到事件的時候,會調用該方法 @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(MessageEvent messageEvent){ Log.d("cylog","receive it"); textView.setText(messageEvent.getMessage()); Toast.makeText(MainActivity.this, messageEvent.getMessage(), Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { super.onDestroy(); //解除注冊 EventBus.getDefault().unregister(this); } }
4、發送事件
與觀察者模式對應的,當有事件發生,需要通知觀察者的時候,被觀察者會調用 notifyObservers() 方法來通知所有已經注冊的觀察者,在 EventBus 中,對觀察者模式底層進行了封裝,我們只需要調用以下代碼就能把事件發送出去:
EventBus.getDefault().post(EventType eventType);
上述 EventType 就是第一步定義的事件類型。
5、threadMode
POSTING
默認的模式,開銷最小的模式,因為聲明為 POSTING 的訂閱者會在發布的同一個線程調用,發布者在主線程那么訂閱者也就在主線程,反之亦,避免了線程切換,如果不確定是否有耗時操作,謹慎使用,因為可能是在主線程發布。
MAIN
主線程調用,視發布線程不同處理不同,如果發布者在主線程那么直接調用(非阻塞式),如果發布者不在主線程那么阻塞式調用,這句話怎么理解呢,看下面的 Log 比較清晰的理解
主線程(阻塞式):
Log.d(TAG, "run : 1"); EventBus.getDefault().post(text);//發送一個事件 Log.d(TAG, "run : 2"); EventBus.getDefault().post(text);//發送一個事件 @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent1(String text) { Log.d(TAG, "onMessageEvent1 : "); }
日志輸出
: run : 1 : onMessageEvent1 : : run : 2 : onMessageEvent1 :
非主線程(非阻塞式):
final String text = "長江長江我是黃河"; new Thread(new Runnable() { @Override public void run() { Log.d(TAG, "run : 1"); EventBus.getDefault().post(text);//發送一個事件 Log.d(TAG, "run : 2"); EventBus.getDefault().post(text);//發送一個事件 } }).start();
日志輸出:
run : 1 run : 2 onMessageEvent1 : onMessageEvent1 :
MAIN_ORDERED
和MAIN差不多,主線程調用,和 MAIN 不同的是他保證了 post 是非阻塞式的(默認走 MAIN 的非主線程的邏輯,所以可以做到非阻塞)
BACKGROUND
在子線程調用,如果發布在子線程那么直接在發布線程調用,如果發布在主線程那么將開啟一個子線程來調用,這個子線程是阻塞式的,按順序交付所有事件,所以也不適合做耗時任務,因為多個事件共用這一個后台線程
ASYNC
在子線程調用,總是開啟一個新的線程來調用,適用於做耗時任務,比如數據庫操作,網絡請求等,不適合做計算任務,會導致開啟大量線程
6、原理分析:
這里並不打算分析具體的源碼邏輯,而是個人在看了源碼之后的筆記。幫助自己更好的理解 eventbus 的實現原理,梳理清楚每一條邏輯。
想看源碼分析可以參考這篇文章:
6.1 單例
/** Convenience singleton for apps using a process-wide EventBus instance. */ public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance;
這里在生成單例的時候使用了雙重檢驗,避免多線程過程中重復創建。其次這里使用到了,類縮而非對象鎖。
對象鎖是用來控制實例方法之間的同步,而類鎖是用來控制靜態方法(或者靜態變量互斥體)之間的同步的。
類鎖只是一個概念上的東西,並不是真實存在的,他只是用來幫助我們理解鎖定實例方法和靜態方法的區別的。
java 類可能會有很多對象,但是只有一個 Class (字節碼)對象,也就是說類的不同實例之間共享該類的 Class 對象。Class 對象其實也僅僅是 1 個 java 對象,只不過有點特殊而已。
由於每個 java 對象都有1個互斥鎖,而類的靜態方法是需要 Class 對象。所以所謂的類鎖,只不過是 Class 對象的鎖而已。
6.2 以 class 為 key 來存儲方法信息
例如一個 activity 里面注冊了一個 eventbus。我們每次進入activity 的時候,都會把 this 傳進去,然后走一遍注冊邏輯,所以你覺得內部是如何存儲注冊對象的呢?是按照 this 來的?
其實內部是通過 class 來存儲的。
public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); //subscriberMethods返回的是subscriber這個類中所有的訂閱方法 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod);//分類保存后面有分析 } } }
通過上面代碼,我們可以看到會去獲取當前對象的類名,然后在通過反射的形式獲取該類的所有方法,從中找到訂閱方法,方便以后發布消息。
如果采用對象保存,每次進入,都是一個不同的對象,然后通過對象再去獲取方法信息,這樣做太費力,也太耗內存了。通過類名的方式,只是第一次比較耗時,后面就方便了。
添加新方法,或者新的事件的時候,會重新編譯,重新獲取一遍新的數據的。
PS : 注冊本身還是掛在對象上的,當對象銷毀的時候,也會進行注銷。
6.3 如何保存訂閱同一事件的不同類
根據事件類型,將注冊同一個事件類型的 class 放在一起。
// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<?> eventType = subscriberMethod.eventType;//訂閱函數參數類型 //這一步很簡單就是在構造函數中記錄下訂閱者和訂閱方法 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //CopyOnWriteArrayList是java.util包下的,他使用了寫時復制的方法來實現,其效率並不高,但可以保證在多線程環境下最終(強調是最終)數據的一致性 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);//subscriptionsByEventType可以根據參數類型來獲取到訂閱事件 //在操作第一個訂閱事件時肯定是==null的 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; } } //typesBySubscriber可以根據訂閱者來獲取到所有的訂閱方法參數類型 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (subscriberMethod.sticky) {//粘性事件的處理邏輯在最后再分析,因為其內容包含了post流程 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); } } }
對 subscriptionsByEventType typesBySubscriber 完成數據初始化,subscriptionsByEventType 根據參數類型存儲訂閱者和訂閱方法,typesBySubscriber 根據訂閱者存儲了所有的參數類型,subscriptionsByEventType 主要是 post 時使用,因為其存儲了訂閱者和訂閱事件這兩個參數在反射時要用到,typesBySubscriber 在反注冊時可以根據訂閱者獲取到存儲的事件類型就可以從 subscriptionsByEventType 中獲取到對應的訂閱者和訂閱方法釋放資源,還可以用來判斷是否注冊。
6.4 如何找到訂閱方法
從緩存中獲取訂閱方法列表,如果緩存中不存在則通過反射獲取到訂閱者所有的函數,遍歷再通過權限修飾符,參數長度(只允許一個參數),注解(@Subscribe) 來判斷是否是具備成為訂閱函數的前提,具備則構建一個 SubscriberMethod (訂閱方法,其相當於一個數據實體類,包含方法,threadmode,參數類型,優先級,是否粘性事件這些參數),循環結束訂閱函數列表構建完成添加進入緩存
6.5 如何在子線程發布消息后在主線程處理
HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) { super(looper); this.eventBus = eventBus; this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage; queue = new PendingPostQueue();//采用獨立隊列,與backgroundPoster一致 }
可以看到,HandlerPoster 自身攜帶一個 looper,主要傳入 mainLooper,就可以處理主線程的事物了。
6.6 是如何調用訂閱方法的
通過反射的形式調用。
void invokeSubscriber(Subscription subscription, Object event) { try { //這里最后說明一下subscription中包含了訂閱者和訂閱方法 event是Post的參數 這里通過反射直接調用訂閱者的訂閱方法 完成本次通信 subscription.subscriberMethod.method.invoke(subscription.subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } }
6.7 如何確定優先級
每次添加的時候,就會根據優先級來添加,優先級越高的,添加在最前面。
Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //CopyOnWriteArrayList是java.util包下的,他使用了寫時復制的方法來實現,其效率並不高,但可以保證在多線程環境下最終(強調是最終)數據的一致性 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);//subscriptionsByEventType可以根據參數類型來獲取到訂閱事件 //在操作第一個訂閱事件時肯定是==null的 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; }
}
6.8 既然存在多線程,是如何保存數據的?
public void post(Object event) { //currentPostingThreadState是ThreadLocal,ThreadLocal可以解決多線程的並發訪問問題,他會為每一個線程提供一個獨立的變量副本,可以隔離多個線程對數據的訪問沖突 PostingThreadState postingState = currentPostingThreadState.get();
...... }
ThreadLocal 的是一個本地線程副本變量工具類。主要用於將私有線程和該線程存放的副本對象做一個映射,各個線程之間的變量互不干擾,在高並發場景下,可以實現無狀態的調用,特別適用於各個線程依賴不通的變量值完成操作的場景。
final static class PostingThreadState { // 通過post方法參數傳入的事件集合 final List<Object> eventQueue = new ArrayList<Object>(); boolean isPosting; // 是否正在執行postSingleEvent()方法 boolean isMainThread; Subscription subscription; Object event; boolean canceled; }
Subscription(Object subscriber, SubscriberMethod subscriberMethod) { this.subscriber = subscriber; this.subscriberMethod = subscriberMethod; active = true; }
訂閱方法的信息:
public SubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky) { this.methodName = methodName; this.threadMode = threadMode; this.eventType = eventType; this.priority = priority; this.sticky = sticky; }
可以發現,基本上所有的信息都被包含在 PostingThreadState 中了,這樣在 post 的方法中就不要額外依賴其他數據了。
6.9 發送消息邏輯過程是怎樣的
post () 發送消息,首先得獲取當前線程的一個發送隊列。從隊列里面依次取出 event ,根據 event.getClass()來獲取保存的訂閱者。
synchronized (this) { //這里根據我們注冊的時候總結 這個容器中裝的是訂閱者和訂閱方法,現在根據發送事件的類型來獲取到對應的訂閱者和訂閱方法這些參數是反射必須要用到的 subscriptions = subscriptionsByEventType.get(eventClass); }
找到訂閱者以后,依次循環,對每個訂閱者進行處理:
//這里根據是否在主線程和threadmode來判斷 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event);//post在什么線程就直接調用 不需要切換線程 break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event);//如果Post在主線程直接調用,反之通過handler來切換到主線程再調用反射 } break; case MAIN_ORDERED: if (mainThreadPoster != null) {//默認走這里的邏輯和MAIN一致 事件排隊等待調用,非阻塞式 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 {//如果post在子線程直接在Post線程調用 invokeSubscriber(subscription, event); } break; case ASYNC: asyncPoster.enqueue(subscription, event);//總是開啟線程來調用 break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
調用 enqueue 后,用於切換線程來處理事件,最后還是會通過反射的形式進行調用。
6.10 黏性事件如何保存和發送
主要使用場景是:當訂閱者尚未創建,先調用 EventBus.getDefault().postSticky() 方法發送一個 sticky 事件,該事件會被 stickyEvents 緩存起來,當訂閱該事件的類調用 register() 方法時,最終會將保存的事件全部發給新注冊的訂閱者一份,因此,新的訂閱者同樣可以收到該事。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //獲取subsrciberMethod傳遞的自定義EventType參數的運行時的類 Class eventType = subscriberMethod.eventType; //Subscription用於綁定subscriber和sucriberMethod,一個訂閱者可以有多個subscriberMethod Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //根據EventType的運行時類取到該類所有的subscriptioins,subscriptionsByEventType是HashMap中的key CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); //若根據EventType找不到subscriptions,則eventType作key,subscriptions作value添加到subscriptionByEventType中。 subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { //已經存在newSubscription,拋出異常該訂閱者已經注冊,不可重復注冊同一個subscriber throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } int size = subscriptions.size(); for (int i = 0; i <= size; i++) { //循環subscriptions,根據標記優先級的priority從高到低,將新的subscription插入到subscriptions中 if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } //typesBySubscriber是一個HashMap,根據subscriber做key,獲取該subscriber對應的所有的訂閱事件的類型 List> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); //該訂閱者之前的訂閱事件類型列表為空,則將當前訂閱類型添加到typesBySubscriber中 typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); //如果該方法被標識為sticky事件 if (subscriberMethod.sticky) { if (eventInheritance) { eventInheritance標識是否考慮EventType的類層次結構 //循環所有的sticky黏性事件 Set, Object>> entries = stickyEvents.entrySet(); for (Map.Entry, Object> entry : entries) { Class candidateEventType = entry.getKey(); //如果當前事件是其他事件的同類型的或者是他們的父類 if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); heckPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
從上面我們可以知道,最后都會調用一個方法:
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { if (stickyEvent != null) { // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state) // --> Strange corner case, which we don't take care of here. postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper()); } }
最后,也會調用到所有事件不管是不是黏性都會走的一個方法:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { //根據@subscriber中threadMode進行區分,POSTING為當前線程執行, //MAIN為主線程,BACKGROUND為子進程,ASYNC為異步執行。 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); } }
最后調用的邏輯還是一樣的。
最后,附上一張 eventbus 的思維導圖,幫助你們更好的去理解 eventbus。