EventBus高效使用及源碼解析

引言

EventBus是greenrobot發布的一個用於事件訂閱和發布的框架,其最大的貢獻在於將事件的訂閱和發布很好地解耦,使代碼更優雅,邏輯更清晰。

EventBus的主要特點如下:

  • 組件解耦
  • 解耦事件訂閱和發布者
  • 在Activitives,Fragments和後台線程的使用中表現良好
  • 避免了複雜且易導致錯誤的依賴和生命周期問題
  • 簡化代碼
  • 足夠快
  • 輕量(大約50K)
  • 已經在100,000,000+個應用上得到了證明
  • 具有一些高級特色,如負責傳遞的線程,訂閱優先順序,粘性事件等.

下面就讓我們一起揭開EventBus的神秘面紗。

1.EventBusAnnotationProcessor:EventBus的正確打開方式

網上有很多介紹EventBus的文章,但是幾乎沒有提到EventBusAnnotationProcessor的。實際上,從EventBus 3開始引入了註解,它的主要作用在於使用註解而非反射來解析訂閱信息,並且這個過程是在編譯時而非運行時完成的,因而可使EventBus中的事件訂閱節約很多時間。

要使用EventBus 3的這個新特性,需要以下幾步:

  • 添加依賴
  • compile org.greenrobot:eventbus:3.0.0

    + 由於註解依賴android-apt-plugin,故需要在項目的gradle的dependencies中引入apt,如下:

    classpath com.neenbedankt.gradle.plugins:android-apt:1.8

    + 在app module的build.gradle中應用apt插件,並設置apt生成的索引的包名和類名,如果不設置的話在編譯時會報錯。

    apply plugin: com.neenbedankt.android-apt apt { arguments { eventBusIndex "wang.imallen.eventbusannotationsample.MyEventBusIndex" } }

    + 最後,需要在app module的dependencies中引入EventBusAnnotationProcessor:

    apt org.greenrobot:eventbus-annotation-processor:3.0.1

    完成以上幾步後,重新編譯一次,即可在app/build/generated/source/apt/debug/下看到生成的MyEventBusIndex類,如下是我的示例中生成的代碼:

    /** This class is generated by EventBus, do not edit. */public class MyEventBusIndex implements SubscriberInfoIndex { private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo(ThirdActivity.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onStickyEvent", wang.imallen.eventbusannotationsample.bean.UserInfo.class, ThreadMode.MAIN, 0, true), })); putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onEvent", wang.imallen.eventbusannotationsample.simple.Event.class, ThreadMode.MAIN), new SubscriberMethodInfo("onEventPosting", wang.imallen.eventbusannotationsample.threadmode.SecondEvent.class), new SubscriberMethodInfo("onEventBackground", wang.imallen.eventbusannotationsample.threadmode.ThirdEvent.class, ThreadMode.BACKGROUND), new SubscriberMethodInfo("onEventAsync", wang.imallen.eventbusannotationsample.threadmode.FourthEvent.class, ThreadMode.ASYNC), })); } private static void putIndex(SubscriberInfo info) { SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info); } @Override public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if (info != null) { return info; } else { return null; } }}

    需要注意的是,項目中至少要有一個訂閱信息,否則EventBusProcessor獲取到的訂閱信息為空,自然不生成相應的類了。

    重新編譯之後,在第一次使用EventBus之前(如Application或SplashActivity中),添加如下代碼,以使Index生效:

    EventBus eventBus=EventBus.builder().addIndex(new MyEventBusIndex()).build();

    至於EventBus中常規事件和sticky事件的發布和訂閱,都是非常簡單的事情,也不是本文的重點,故不再贅述,就有一點需要注意,EventBus 3中sticky events的訂閱是在註解中添加類似@Subscriber(sticky=true,threadMode=ThreadMode.MAIN)的屬性.

    讀者可以fork我在github中的這個示例進行了解: EventBusProcessor使用示例

    需要注意的是,如果不利用EventBusAnnotationProcessor,則EventBus 3的解析速度反而會比之前版本更慢。如下是square發布的EventBus3與之前版本在Nexus One,Nexus 5,Nexus 9上的表現對比:


  • 2.EventBus 3.0.0架構

    EventBus 3.0.0的發布和訂閱事件的架構如下:

    仍然是在某個線程發布後,通過EventBus分發到不同線程中的Subscriber,這一點與之前版本的並無不同。

    EventBus 3.0.0的UML圖如下:

    需要重點關注的類有EventBus,SubscriberMethodFinder,FindState,SubscriberInfo,PostingThreadState,HandlerPoster,BackgroundPoster,

    AsyncPoster,PendingPost,Subscription,SubscriberMethod,後面的分析也主要與這些類有關.

3.事件訂閱/解除訂閱

下面的代碼講解將結合 EventBusProcessor使用示例 進行。

事件訂閱的代碼如下:

public void register(Object subscriber) { //subscriberClass是類似wang.imallen.eventbusannotationsample.MainActivity這樣的 Class<?> subscriberClass = subscriber.getClass(); //subscriberMethods是類似MainActivity中的onEvent(),onEventAsync(),onEventBackground(),onEventMain(),onEventPosting(),onStickyEvent()這樣的 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }

subscriberClass就是訂閱者所屬的Class,如MainActivity.class,之後利用subscriberMethodFinder查找subscriberClass中的訂閱方法,其中findSubscriberMethods()方法如下:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //ignoreGeneratedIndex默認為false,因為反射成本高 if (ignoreGeneratedIndex) { subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }

由於反射成本高,而且EventBus 3引入了EventBusAnnotationProcessor,故默認ignoreGeneratedIndex為false,需要注意的是,如果設置ignoreGeneratedIndex為true,則前面使用的MyEventBusIndex無效,還是會走反射解析的分支。

要證實這一點很簡單,進入findUsingReflection()方法看一下即可:

知乎專欄 - 隨心寫作,自由表達3.1 使用反射獲取訂閱信息

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { findUsingReflectionInSingleClass(findState); findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }

調用的是findUsingReflectionInSingleClass(),其代碼如下:

private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities //methods是所有聲明的方法,不包括只在基類的方法,如MainActivity中就是onCreate(),onDestroy(),onEvent(),onEventAsync(),onEventBackground(),onEventMain(),onEventPosting(),onStickEvent(),openSecondActivity() methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; } for (Method method : methods) { int modifiers = method.getModifiers(); //必須是public和非static,非abstract,非bridge,非synthetic的方法 if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { //evnetType是類似wang.imallen.eventbusample.simple.Event這樣的事件類型 Class<?> eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } } }

代碼邏輯很簡單,就是先獲取這個類的declared方法,然後選擇其中是public和非static,非abstract,非bridge,非synthetic的方法,如果該方法的註解為Subscribe,則說明它是訂閱事件的方法,解析註解參數,最後將解析結果到findState.subscriberMethods中.

裡面有個小細節就是Class<?>eventType=parameterTypes[0],這意味著即使訂閱方法中有多個參數,也只取第一個,如果確認該訂閱對象中還未添加該eventtype的方法,則添加到findState.subscriberMethods中,其中checkAdd()方法的代碼很簡單,不展開分析了。

從這裡引出一個細節:在一個訂閱對象中,同一個事件類型只能有一個回調。

3.2 使用EventBusAnnotationProcessor解析結果獲取訂閱信息

回到findSubscriberMethods()這個方法,ignoreGeneratedIndex為false時,通過findUsingInfo()方法來獲取訂閱信息:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); //使用while而不是if的原因是findState.moveToSuperclass()會切換findState.clazz對象為基類Class對象,從而循環解析 while (findState.clazz != null) { findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { //如果發現獲取不到subscriberInfo的話,就還是要使用反射來獲取 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }

進入getSubscriberInfo()查看:

private SubscriberInfo getSubscriberInfo(FindState findState) { if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo(); if (findState.clazz == superclassInfo.getSubscriberClass()) { return superclassInfo; } } if (subscriberInfoIndexes != null) { for (SubscriberInfoIndex index : subscriberInfoIndexes) { SubscriberInfo info = index.getSubscriberInfo(findState.clazz); if (info != null) { return info; } } } return null; }

這裡由於subscriberInfo為null,故執行的是第二個分支,即在subscriberInfoIndexes中尋找,那這個subscriberInfoIndexes來自哪裡呢?

可以看到,它是在constructor中被賦值的,而subscriberMethodFinder就在EventBus的constructor中創建的,這個subscriberInfoIndexes來自builder.subscriberInfoIndexes,而EventBusBuilder中的subscriberInfoIndexes就是來自於第一節中我們提到的如下代碼:

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

所以,MyEventBusIndex起作用的地方是在SubscriberMethodFinder中,結合MyEventBusIndex的代碼可知,EventBusAnnotationProcessor解析出訂閱信息,之後以訂閱對象的Class為key放入到HashMap中(有的例外的是sticky events,這個在後面會分析),然後在SubscriberMethodFinder的getSubscriberInfo()中,根據findState.clazz來解析已有的結果,如果查找到了則直接返回。

回到findUsingInfo()中,仍然是要檢查是否已添加同類型事件的回調,如果沒有添加才會添加到findState.subscriberMethods中.

需要注意的是else中findUsingReflectionInSingleClass(findState),我個人認為是冗餘代碼,除非EventBusAnnotationProcessor解析出錯或不完整,某種程度上是對EventBusAnnotationProcessor不自信的表現,期待作者在後面去掉吧。

注意return語句之前的findState.moveToSuperclass()這句,它其實是將findState中的clazz對象換成基類的,也就是說,事件訂閱是可以繼承的,之後循環之前的過程。

3.3 綁定訂閱對象和方法

回到EventBus#register()中,在獲取subscriberMethods之後,就是遍歷各訂閱方法,逐個綁定。subscribe()的代碼如下:

// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //eventType是類似wang.imallen.eventbusannotationsample.simpel.Event這樣的 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(); //注意這裡是i<=size,是為了考慮到subscriptions為空的情況也能添加newSubscription到subscriptions中 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<>(); //subscriber是類似MainActivity,而subscribedEvents是類似MainActivity中涉及到的所有eventType,如Event.class,FirstEvent.class,SecondEvent.class,...,UserInfo.class typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (subscriberMethod.sticky) { //eventInheritance表示是否要考慮繼承 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(); //給這個新的訂閱者發送所有eventType類型或其子類的事件 checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { //如果不考慮繼承,則只要給新的訂閱者發送eventType這個類型的最近一次的粘性事件 Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }

這個方法略長,分為以下幾步分析:

知乎專欄 - 隨心寫作,自由表達

  • 首先根據subscriber和subscriberMethod生成一個Subscription對象
  • 之後從subscriptionsByEventType這個Map中獲取對應eventType的所有Subscription對象,如果為空則新建,否則判斷是否已經包含newSubscription,如包含則拋出已訂閱的異常
  • 遍歷subscriptions,如果發現subscriberMethod的優先順序比某個Subscription對象中的優先順序高,則用當前的Subscription對象取代它,之後跳出循環
  • typesBySubscriber是以訂閱對象為key,事件類型列表為value的Map,將當前的事件類型添加到對應的List中即可
  • 最後一部分比較特殊,如果訂閱的是粘性事件,則要分兩種情況:
  • 如果要考慮事件繼承(EventBusBuilder中默認為true),則需要遍歷stickyEvents這個以事件的Class為key,事件本身為value的Map,如果發現某個事件類型是eventType的子類(就是eventType.isAssignableFrom(candidateEventType)的含義),則要發布這個事件類型對應的事件(也就是該事件類型最近一次事件)給這個訂閱方法。至於事件發布的代碼,在第4節會分析。
  • 如果不考慮事件繼承,則只需要將eventType最近一次的事件(如果有的話)發送給這個訂閱方法即可

3.4 解除訂閱

解除訂閱的代碼如下:

/** Unregisters the given subscriber from all event classes. */ public synchronized void unregister(Object subscriber) { List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }

代碼很簡單,就是解除與該訂閱對象關聯的所有事件類型.unsubscribeByEventType()的代碼如下:

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */ 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--; } } } }

該方法就是根據事件類型獲取所有的訂閱信息,如果該訂閱信息的訂閱對象為當前訂閱對象,則將其移除.

解除訂閱的邏輯很簡單,就不畫流程圖了.

知乎專欄 - 隨心寫作,自由表達3.5 事件訂閱總結

至此,事件訂閱就基本分析完了,我們可以從中梳理出如下流程:

其中紅線標出的是我認為不合理的地方。

4.事件發布

4.1 發布普通事件

post()的代碼如下:

/** Posts the given event to the event bus. */ 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 { while (!eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }

  • 首先獲取當前線程的PostingThreadState對象,實現方式是ThreadLocal

  • 然後將當前event添加到postingState的事件隊列中

  • 如果當前線程沒有正在發布事件,則遍歷當前線程的事件隊列,將事件逐個發布出去,其中postSingleEvent()的代碼如下

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; 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); } if (!subscriptionFound) { if (logNoSubscriberMessages) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } }

這裡涉及到事件繼承的問題(默認情況下eventInheritance為true,即要考慮事件繼承),如果考慮事件繼承,則要獲取這個事件類型的所有基類和實現的介面,並且還要將基類的基類,以及介面的基類也包含進去。如下是lookupAllEventTypes()的代碼:

/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */ private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) { synchronized (eventTypesCache) { List<Class<?>> eventTypes = eventTypesCache.get(eventClass); if (eventTypes == null) { eventTypes = new ArrayList<>(); Class<?> clazz = eventClass; while (clazz != null) { eventTypes.add(clazz); addInterfaces(eventTypes, clazz.getInterfaces()); clazz = clazz.getSuperclass(); } eventTypesCache.put(eventClass, eventTypes); } return eventTypes; } }

代碼很簡單,就是循環獲取基類以及基類的介面,並且將符合條件的事件類型添加到eventTypes中,最後將(eventClass,eventTypes)放入eventTypesCache中。

再回到postSingEvent()中,下面就是遍歷eventTypes了,進入postSingleEventForEventType()中,代碼如下:

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; }

如果eventClass對應的訂閱信息不為空,則將當前事件逐個發布到各訂閱對象中,真正的發布處理在postToSubscription()中,代碼如下:

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 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); } }

顯然,需要分線程模式為POSTING,MAIN,BACKGROUND,ASYNC這四種情況處理。

  • 如果是POSTING模式,則直接回調事件訂閱方法,說明POSTING模式代表發布和訂閱回是在同一個線程;
  • 如果要在主線程回調,而發布線程就是主線程的話,則直接回調;否則需要利用主線程發布代理(mainThreadPoster)來進行發布;
  • 如果要在後台線程中回調,而發布線程是主線程的話,則需要利用後台線程發布代理(backgroundPoster)來進行發布,否則直接發布;
  • 如果是非同步發布,則不管當前是在什麼線程,都是利用非同步發布代理(asyncPoster)來進行發布
  • 如果threadMode不是其中之一的話,則拋出異常.

到這裡為止,普通事件的發布就基本梳理完了。

4.2 發布sticky event

粘性事件的發布代碼如下:

/** * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky * event of an events type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}. */ public void postSticky(Object event) { synchronized (stickyEvents) { stickyEvents.put(event.getClass(), event); } // Should be posted after it is putted, in case the subscriber wants to remove immediately post(event); }

非常簡單,相比普通事件的發布,就只是多了一步:將當前事件put到當前事件類型對應的實體中。這樣做的目的是為了在綁定粘性事件回調時可以將最近一次該事件類型的事件發布給它。

至此,我們可以梳理出發布事件的流程:

推薦閱讀:

Android ConstraintLayout使用指南
Push mail 的實現原理是什麼?
如果只按性能配置來講,oppo R11應該值多少錢?同等性能、配置的其他手機大家有推薦的嗎?
如何評價諾基亞首部安卓手機-諾基亞X?

TAG:Android | Android开发 |