EventBus源码分析
簡介
前面我學習了如何使用EventBus,還有了解了EventBus的特性,那么接下來我們一起來學習EventBus的源碼,查看EventBus的源碼,看看EventBus給我們帶來什么驚喜以及編程思想。
這個圖我們從一開始就一直放置在上面了。我們在來回顧一下,EventBus的官網是怎么定義它的呢?
EventBus是Android和Java的發布/訂閱(觀察者模式)事件總線。
我們大概了解了EventBus的構建思想。接下來我們進入源碼學習吧。
進入源碼分析
我們從EventBus的注冊開始入手。
EventBus.getDefault().register(this); public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {//創建了EventBus實例,進入下面的方法defaultInstance = new EventBus(); }}}return defaultInstance;}上面的方法是一個雙重校驗的單例。
public EventBus() {this(DEFAULT_BUILDER); //private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();}DEFAULT_BUILDER是EventBusBuilder.class實例來創建的,這個類非常重要:使用自定義參數創建EventBus實例,還允許自定義默認EventBus實例,如前面的例子使用索引、配置EventBus的配置&事件的優先級&使用索引(四)等就是通過這個類來實現的,大家可以回顧一下。進入初始化一下必要的參數的構造方法EventBus(EventBusBuilder builder),如下
EventBus(EventBusBuilder builder) {logger = builder.getLogger(); /初始化LoggersubscriptionsByEventType = new HashMap<>(); //存儲訂閱事件的typesBySubscriber = new HashMap<>(); //存儲相關訂閱者class列表,key是注冊者的對象,value是訂閱者的class列表stickyEvents = new ConcurrentHashMap<>(); //粘性事件//下面這4個實例就是我們設置方法``threadMode = ThreadMode.xxx``//*1mainThreadSupport = builder.getMainThreadSupport();mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;backgroundPoster = new BackgroundPoster(this);asyncPoster = new AsyncPoster(this);//獲取注冊者信息索引(添加由EventBus的注釋預處理器生成的索引)indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;//初始訂閱方法查找器。這個類主要具有查找訂閱的方法,繼續往下看subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,builder.strictMethodVerification, builder.ignoreGeneratedIndex);logSubscriberExceptions = builder.logSubscriberExceptions; //是否有日記訂閱,默認truelogNoSubscriberMessages = builder.logNoSubscriberMessages;//是否有日記信息,默認 truesendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;//是否發送訂閱異常事件,默認truesendNoSubscriberEvent = builder.sendNoSubscriberEvent; //是否發送沒有訂閱事件,默認truethrowSubscriberException = builder.throwSubscriberException; //是否拋出異常處理//默認情況下,EventBus會考慮事件類層次結構(將通知超類的注冊者)。 關閉此功能將改善事件的發布。對于直接擴展Object的簡單事件類,我們測量事件發布的速度提高了20%。eventInheritance = builder.eventInheritance; executorService = builder.executorService; //創建線程池}我們將此段代碼逐步分析.
這步主要是進行初始化話一下必要的參數,如代碼注解所示。
下面這段代碼就是我們常用的@Subscribe(threadMode = ThreadMode.xxx);初始化。一般常用的就是以下4種。
我們來看看builder.getMainThreadSupport()方法返回的是MainThreadSupport接口,表示為支持Android主線程。
上面的4個線程中都持有 PendingPostQueue 等待發送的隊列實例。
由mainThreadSupport.createPoster(this)創建一個HandlerPoster而該類繼承了Handle,并且初始化了一個等待發布隊列。代碼如下。
上面代碼在這里主要初始化訂閱的方法查找器。下面會講解到它是如何進行訂閱方法的。
我們這這里知道了EventBus初始化,然后相關的實例的創建,接下來我我們進入到register(this)方法的調用如下方法。
上面的代碼表示的是給注冊者接收事件。 傳遞當前所注冊的對象,如Activity、Fragment。
- 注意: 注冊者如果不再接收信息,必須調用unregister(Object)方法,表示解除注冊則該訂閱者將不再接收到數據了,如果不進行解除將可能出現內存泄漏。一般在onDestroy方法解除注冊后面會講解到。 在注冊者中擁有必須由@Subscribe注解的方法。@Subscribe還允許配置ThreadMode和優先級、是否是粘性行為。
接著,我們進入List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);方法。
//private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); //線程安全List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {//從Map集合中獲取訂閱方法列表List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);//判斷當前獲取方法是否為空if (subscriberMethods != null) {return subscriberMethods;}//通過EventBusBuilder.ignoreGeneratedIndex//是否忽略配置索引,默認是忽略索引if (ignoreGeneratedIndex) {//1.通過反射獲取方法列表subscriberMethods = findUsingReflection(subscriberClass);} else {//2.通過索引方式獲取訂閱方法列表subscriberMethods = findUsingInfo(subscriberClass);}//是否訂閱方法為空,如果為空則表示該訂閱者里面的方法都不符合EventBus訂閱方法的規則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;} }我們逐步分析該段代碼。
該段代碼通過注冊者的類來獲取當前dingy的方法列表,如果不為空則直接返回訂閱方法。
通過反射來查找訂閱方法,如果該方法為空則拋出異常:該訂閱者的方法和其超類沒有@Subscriber注解的共有方法(即表示不符合EventBus訂閱方法的規則)。如果不為空則存儲到ConcurrentHashMap集合中。
這里的是由ConcurrentHashMap集合存儲是以當前訂閱者的class為key,而將其由@Subscriber綁定的方法添加到List中,,就是集合的value。必須保持線程安全的,所以這里使用了ConcurrentHashMap。
- 1.是否忽略索引設置,該方法表示忽略設置索引,進入該方法findUsingReflection(Class<?> subscriberClass),通過反射進行獲取List<SubscriberMethod>
- 注:設置索引在EventBus的配置&事件的優先級&使用索引(四)這里說的,大家可以去看看。
上面的代碼主要是通過反射來獲取相關的訂閱方法,里面由靜態內部類FindState進行管理相關信息,由于上面的方法和下面索引的方法都將調用同一個方法,所以放在下面來將講解,請看下面信息。
- 2.是否忽略索引設置,該方法表示的設置了索引,進入findUsingInfo(Class<?> subscriberClass)方法,如下。
分析本段代碼。
首先進入查找的準備狀態,通過默認狀態池返回狀態信息,主要存儲的是FindState實例。主要存儲的有訂閱者的class、訂閱者信息SubscriberInfo。訂閱者@Subscribe方法列表List<SubscriberMethod>等.
如果當前訂閱者class不為空,則將獲取到訂閱者信息。這里分兩種情況。
如果使用添加索引的話,getSubscriberInfo(findState)該方法將獲取到訂閱信息,如果沒有使用索引的話則將調用findUsingReflectionInSingleClass(findState);該方法來獲取信息
- 使用添加索引,并返回訂閱方法,該方法如下
由于AbstractSubscriberInfo類實現了SubscriberInfo接口,而SimpleSubscriberInfo繼承了AbstractSubscriberInfo并實現了getSubscriberMethods方法,代碼如下:
本段代碼在SimpleSubscriberInfo.class中。返回訂閱方法數組SubscriberMethod[]。
- 3.如果沒有添加索引則進入該方法findUsingReflectionInSingleClass(findState);
通過反射來獲取訂閱者方法。這個方法有點長,慢慢看。
private void findUsingReflectionInSingleClass(FindState findState) {Method[] methods;try {// This is faster than getMethods, especially when subscribers are fat classes like Activities//該方法代替getMethods()方法,獲取該訂閱者class全部方法。methods = findState.clazz.getDeclaredMethods();} catch (Throwable th) {// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149methods = findState.clazz.getMethods(); //獲取該訂閱者class全部方法findState.skipSuperClasses = true; //設置跳過超類}for (Method method : methods) {int modifiers = method.getModifiers();//獲取修飾符if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //通過與運算判斷當前的修飾符,是否符合EventBus方法定義Class<?>[] parameterTypes = method.getParameterTypes(); //獲取參數類型if (parameterTypes.length == 1) { //因為EventBus方法中必須有參數,所以當參數為1時,符合要求//通過注解的形式@Subscribe獲取Subscribe,默認的Subscribe為(priority==0,sticky=false,threadMode=POSTING),就是說ThreadMode為POSTING,粘性為false,優先級為0,如果方法中設置了相應的值,則將是你設置的值。如threadMode=MAIN。Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);if (subscribeAnnotation != null) {Class<?> eventType = parameterTypes[0]; //獲取當前參數的class//進行等級檢查,并存儲到Map集合中,該方法checkAdd是FindState.class中的方法 ,key是@Subscriber中參數的class,value是該subscriberMethod.method,也就是@Subscribe中的方法如:public void onMessageEvent(MessageEvent event)if (findState.checkAdd(method, eventType)) { // 獲取ThreadMode ,如MAINThreadMode threadMode = subscribeAnnotation.threadMode();//將訂閱方法添加到列表中findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,subscribeAnnotation.priority(), subscribeAnnotation.sticky()));}}} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { //不符合EventBus訂閱方法的規則要求。拋出異常,提示該方法必須是@Subscribe注解,并且需要一個參數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)) {//是一種非法的@Subscribe方法:必須是公共的,非靜態的,非抽象的String methodName = method.getDeclaringClass().getName() + "." + method.getName();throw new EventBusException(methodName +" is a illegal @Subscribe method: must be public, non-static, and non-abstract");}}}該段代碼主要是獲取該注冊者的全部方法,并進行篩選出來符合EventBus的訂閱方法的規則,通過注解的形式來獲取訂閱方法,最后添加到查找狀態的列表中。
首先獲取的是修飾符,參數類型,再通過注解的形式來獲取訂閱方法。如下注解Subscribe ,如果不符合EventBus相關訂閱方法的規則將拋出異常提示,是否在方法上書寫了錯誤的方法。
接下來我們進入看看@Subscribe 訂閱方法是通過注解的形式來設置的。
接下來進入getMethodsAndRelease(FindState findState)
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {//獲取到訂閱方法列表List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods); findState.recycle(); //清空回收相關信息synchronized (FIND_STATE_POOL) {for (int i = 0; i < POOL_SIZE; i++) {if (FIND_STATE_POOL[i] == null) {FIND_STATE_POOL[i] = findState; //將查找狀態器findState,添加到狀態池FIND_STATE_POOL中,為下次直接從查找狀態池中獲取break;}}}return subscriberMethods; // 返回訂閱方法列表}上面該方法通過FindState靜態內部類獲取了訂閱的列表,然后被存儲到了ConcurrentHashMap集合中。并且存儲到狀態池中,為方便下次讀取。并且回收資源。
獲取到List<SubscriberMethod>訂閱列表后,再次回到了注冊方法中register(Object subscriber)進入到方法subscribe(subscriber, subscriberMethod);該方法通過循環進行遍歷
- 注 此處必須是在線程同步中進行,所以添加synchronized。
上面的方法中主要是通過獲取"事件類型"即訂閱方法中的參數class。然后將其添加到Map集合中,這里用到了CopyOnWriteArrayList(原理:是寫時復制容器,即當我們往CopyOnWriteArrayList容器添加元素時,先從原有的數組中拷貝一份出來,然后在新的容器做寫操作(添加元素),寫完之后,再將原來的數組引用指向到新數組的形式存儲數據的,它是線程安全的。)
然后進行判斷是否設置優先級,遍歷并添加到subscriptions列表中。
通過當前的注冊者,獲取注冊者class列表,并添加到typesBySubscriber集合中,key是注冊者的class,如:Activity,而value就是訂閱方法中的參數class的列表。這個地方說明了,一個注冊者,可以擁有多個訂閱事件(方法),將其綁定起來,存儲到Map集合中。最后將該參數class添加到subscribedEvents.add(eventType);列表中。
下面方法是檢查粘性事件訂閱發送事件方法,如下
- 注:這里主要是檢查是否發送粘性發送事件,方法 postToSubscription我們到發送事件POST方法調用時講解。
- 事件發送
接下來我進入post方法,事件發送。此時,我們回憶一下簡介中的圖:
post()==>EventBus==>將分發到各個訂閱事件中。也就是訂閱方法。我們來看看是如何進行操作的。代碼如下
通過以上代碼我大概的知道,post()方法里面的參數是一個實體的對象,而該實體就是我們在訂閱方法中的參數實體。你發現了什么了嗎?
前邊我們已經分析了,該注冊者的訂閱方法主要的存儲過程,接下來我們一起進入探究吧。方法跟蹤進入post()方法。
currentPostingThreadState.get()方法中我們知道
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {@Overrideprotected PostingThreadState initialValue() {return new PostingThreadState();}};通過ThreadLocal這里就叫"本地線程"吧,來管理當前的發送線程狀態,每個發送線程將得到對應一個PostingThreadState,而該PostingThreadState管理eventQueue信息,該信息主要存儲的是發送事件。
通過eventQueue.add(event)存儲該發送事件以后完成以后,就判斷當前的發送事件狀態是否是正在發送中,如果還沒發送則當該eventQueue不為為空的時候,進入循環發送該事件,發送完一個就刪除一個,直到發送完成為止。
進入方法postSingleEvent(Object event, PostingThreadState postingState)中。
上面的方法首先獲取該事件類型的class然后傳遞到lookupAllEventTypes(eventClass)方法中,通過該方法獲取當前發送事件的class,包括父類、實現的接口等等列表。所以一般情況下會返回當前發送的事件,和Object.class(注:當然了,如果該事件實現接口的話,也會包括實現的接口的。)列表即List<Class<?>>,然后進行遍歷進行或運算。
我們先進入lookupAllEventTypes(eventClass)方法,看看返回的事件類型,并且知道他存儲到Map集合中,即key是當前事件class,value是該事件的所有類型,包括父類,接口等。
看看方法postSingleEventForEventType(event, postingState, clazz),發送事件
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {//通過當前的事件class,獲取訂閱事件列表subscriptions = subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {for (Subscription subscription : subscriptions) {//對PostingThreadState進行賦值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;}大家是否還記得在注冊的時候出現的CopyOnWriteArrayList<Subscription>在這里就用到了,通過發送事件的class獲取CopyOnWriteArrayList容器里面的訂閱事件信息,包括注冊者對象,訂閱方法等。然后遍歷并進行發送事件。在此方法發布postToSubscription()事件
/*** subscription:發布訂閱的事件處理器* event:當前發布的事件* isMainThread:是否是在主線程中*/ 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 subscriberinvokeSubscriber(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);}}好了,這就是之前我們在注冊中有一個地方遺留下的,就是判斷是否是粘性狀態是調用的方法,大家往回看,就看到了在方法checkPostStickyEventToSubscription中有調用到該方法。
這里會根據,在方法中所選的threadMode進行調用,
- 1.MAIN,如果當前是否是UI線程,則會進入到invokeSubscriber(subscription, event);方法中。否則將調用mainThreadPoster.enqueue(subscription, event);這個方法將交給HandlerPoster.class里面的enqueue(Subscription subscription, Object event)方法,HandlerPoster.class現實了該接口,移交給handle完成,大家可以進入HandlerPoster.class看看,這里就不說了。
- 2.POSTING直接調用 invokeSubscriber(subscription, event);
- 3.MAIN_ORDERED如果當前mainThreadPoster不為空則調用 mainThreadPoster.enqueue(subscription, event);和MAIN進入HandlerPoster.class,反之調用invokeSubscriber(subscription, event);
- 4.BACKGROUND如果當前是主線程,則調用backgroundPoster.enqueue(subscription, event);,反之調用invokeSubscriber(subscription, event);
- 5.ASYNC,異步方法調用,執行 asyncPoster.enqueue(subscription, event);,方法進行跟蹤,該類有實現了Runnable接口,然后調用eventBus.invokeSubscriber(pendingPost);,最終將調用了invoke方法。
invoke()這是一個本地的方法,將調用C/C++的到方法進行發布。最后將進入到訂閱方法中。并傳遞該事件到到訂閱的方法中。
好了,這里我們將post發布事件的方法簡單的分析了一下。整個EventBus的分析差不多完成了,但是,我們還有一點不能忘記就是解除綁定。接下來我們來看看解除綁定的方法。
- 注:該方法一般生命周期的onDestroy()方法中進行解除綁定。
代碼如下:
EventBus.getDefault().unregister(this);直接進入unregister(this)
/** 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 {logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());}} /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. *//*** subscriber:指代當前解除注冊的對象如Activity,* eventType:發送的事件類型,post函數的實體對象class*/ 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--;}}} }解除綁定,這里很簡單。這里就不一一進行解釋了,^_^。
總結
1.本篇文章主要對EventBus源碼進行簡單的分析。主要是進行了觀察者模式的一些高級運用。如果大家對觀察者模式理解不怎么清楚可以進入這里看看簡單的案例觀察者模式,內容非常簡單。
2.相關的EvenBut的使用,請看之前的內容。如EventBus認識(一)、EventBus的ThreadMode使用以及分析(二)等等。
3.學習本篇文章中可以認識到一些常用的類如CopyOnWriteArrayList、ThreadLocal等,到時可以深入研究一下,有助我們提高。
4.一些編程思想,設計模式值得我們去學習的,如單例模式EventBus雙重校驗、建造者模式,EventBus構造器的時候用到,用了初始化各個參數等等。面向接口編程而不針對實現編程。
5.如果有什么問題希望大家進行指正,好好學習,一起進步。
總結
以上是生活随笔為你收集整理的EventBus源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10. Python面向对象
- 下一篇: 2019年1月3日