編輯:關於Android編程
上一篇我們講到了EventBus3.0的用法,這一篇我們來講一下EventBus3.0的源碼以及它的利與弊。
當我們要調用EventBus的功能時,比如注冊或者發送事件,總會調用EventBus.getDefault()來獲取EventBus實例:
public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }
很明顯這是一個單例模式,采用了雙重檢查模式 (DCL)
接下來看看new EventBus()做了什麼:
public EventBus() { this(DEFAULT_BUILDER); }
這裡DEFAULT_BUILDER是默認的EventBusBuilder,用來構造EventBus:
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
this調用了EventBus另一個構造函數:
EventBus(EventBusBuilder builder) { subscriptionsByEventType = new HashMap<>(); typesBySubscriber = new HashMap<>(); stickyEvents = new ConcurrentHashMap<>(); mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10); backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0; subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; }
獲取到EventBus後,便可以將訂閱者注冊到EventBus中,下面來看一下register方法:
public void register(Object subscriber) { Class subscriberClass = subscriber.getClass(); // 用 subscriberMethodFinder 提供的方法,找到在 subscriber 這個類裡面訂閱的內容。 ListsubscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
findSubscriberMethods找出一個SubscriberMethod的集合,也就是傳進來的訂閱者所有的訂閱的方法,接下來遍歷訂閱者的訂閱方法來完成訂閱者的訂閱操作。對於SubscriberMethod(訂閱方法)類中,主要就是用保存訂閱方法的Method對象、線程模式、事件類型、優先級、是否是粘性事件等屬性。下面就來看一下findSubscriberMethods方法:
ListfindSubscriberMethods(Class subscriberClass) { //從緩存中獲取SubscriberMethod集合 List subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //ignoreGeneratedIndex屬性表示是否忽略注解器生成的MyEventBusIndex if (ignoreGeneratedIndex) { //通過反射獲取subscriberMethods subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } //在獲得subscriberMethods以後,如果訂閱者中不存在@Subscribe注解並且為public的訂閱方法,則會拋出異常。 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; } }
首先從緩存中查找,如果找到了就立馬返回。如果緩存中沒有的話,則根據 ignoreGeneratedIndex 選擇如何查找訂閱方法,ignoreGeneratedIndex屬性表示是否忽略注解器生成的MyEventBusIndex。不了解MyEventBusIndex的同學可以查看【Bugly干貨分享】老司機教你 “飙” EventBus 3這篇文章。最後,找到訂閱方法後,放入緩存,以免下次繼續查找。ignoreGeneratedIndex 默認就是false,可以通過EventBusBuilder來設置它的值。我們在項目中經常通過EventBus單例模式來獲取默認的EventBus對象,也就是ignoreGeneratedIndex為false的情況,這種情況調用了findUsingInfo方法:
private ListfindUsingInfo(Class subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //獲取訂閱者信息,沒有配置MyEventBusIndex返回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 { //通過反射來查找訂閱方法 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
通過getSubscriberInfo方法來獲取訂閱者信息。在我們開始查找訂閱方法的時候並沒有忽略注解器為我們生成的索引MyEventBusIndex,如果我們通過EventBusBuilder配置了MyEventBusIndex,便會獲取到subscriberInfo,調用subscriberInfo的getSubscriberMethods方法便可以得到訂閱方法相關的信息,這個時候就不在需要通過注解進行獲取訂閱方法。如果沒有配置MyEventBusIndex,便會執行findUsingReflectionInSingleClass方法,將訂閱方法保存到findState中。最後再通過getMethodsAndRelease方法對findState做回收處理並反回訂閱方法的List集合。
對於MyEventBusIndex的配置它是一個可選項,所以在這裡就不在進行詳細的介紹,下面就來看一下findUsingReflectionInSingleClass的執行過程:
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities 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(); 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) { 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"); } } }
在這裡主要是使用了Java的反射和對注解的解析。首先通過反射來獲取訂閱者中所有的方法。並根據方法的類型,參數和注解來找到訂閱方法。找到訂閱方法後將訂閱方法相關信息保存到FindState當中。
在查找完所有的訂閱方法以後便開始對所有的訂閱方法進行注冊:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class eventType = subscriberMethod.eventType; //根據訂閱者和訂閱方法構造一個訂閱事件 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //獲取當前訂閱事件中Subscription的List集合 CopyOnWriteArrayListsubscriptions = subscriptionsByEventType.get(eventType); //該事件對應的Subscription的List集合不存在,則重新創建並保存在subscriptionsByEventType中 if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { //訂閱者已經注冊則拋出EventBusException if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } //遍歷訂閱事件,找到比subscriptions中訂閱事件小的位置,然後插進去 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 > subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } //將當前的訂閱事件添加到subscribedEvents中 subscribedEvents.add(eventType); if (subscriberMethod.sticky) { if (eventInheritance) { //粘性事件的處理 Set , Object>> entries = stickyEvents.entrySet(); for (Map.Entry , 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是我們投遞訂閱事件的時候,就是根據我們的EventType找到我們的訂閱事件,從而去分發事件,處理事件的;typesBySubscriber在調用unregister(this)的時候,根據訂閱者找到EventType,又根據EventType找到訂閱事件,從而對訂閱者進行解綁。第二件事,如果是粘性事件的話,就立馬投遞、執行。
在獲取到EventBus對象以後,可以通過post方法來進行對事件的提交:
public void post(Object event) { //PostingThreadState保存著事件隊列和線程狀態信息 PostingThreadState postingState = currentPostingThreadState.get(); //獲取事件隊列,並將當前事插入到事件隊列中 List
首先從PostingThreadState對象中取出事件隊列,然後再將當前的事件插入到事件隊列當中。最後將隊列中的事件依次交由postSingleEvent方法進行處理,並移除該事件。來看看postSingleEvent方法裡做了什麼:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class eventClass = event.getClass(); boolean subscriptionFound = false; //eventInheritance表示是否向上查找事件的父類,默認為true if (eventInheritance) { List> 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,可以通過在EventBusBuilder中來進行配置。當eventInheritance為true時,則通過lookupAllEventTypes找到所有的父類事件並存在List中,然後通過postSingleEventForEventType方法對事件逐一處理,接下來看看postSingleEventForEventType方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) { CopyOnWriteArrayListsubscriptions; //取出該事件對應的Subscription集合 synchronized (this) { subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { //將該事件的event和對應的Subscription中的信息(包擴訂閱者類和訂閱方法)傳遞給postingState 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; }
同步取出該事件對應的Subscription集合並遍歷該集合將事件event和對應Subscription傳遞給postingState並調用postToSubscription方法對事件進行處理,接下來看看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); } }
取出訂閱方法的線程模式,之後根據線程模式來分別處理。舉個例子,如果線程模式是MAIN,提交事件的線程是主線程的話則通過反射,直接運行訂閱的方法,如果不是主線程,我們需要mainThreadPoster將我們的訂閱事件入隊列,mainThreadPoster是HandlerPoster類型的繼承自Handler,通過Handler將訂閱方法切換到主線程執行。
public synchronized void unregister(Object subscriber) { List> 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()); } }
typesBySubscriber我們在訂閱者注冊的過程中講到過這個屬性,他根據訂閱者找到EventType,然後根據EventType和訂閱者來得到訂閱事件來對訂閱者進行解綁。
從EventBus作者提供的圖我們可以看到EventBus的核心架構,其實就是基於觀察者模式來實現的
EventBus好處比較明顯,它能夠解耦和,將業務和視圖分離,代碼實現比較容易。而且3.0後,我們可以通過apt預編譯找到訂閱者,避免了運行期間的反射處理解析,大大提高了效率。當然EventBus也會帶來一些隱患和弊端,如果濫用的話會導致邏輯的分散並造成維護起來的困難。另外大量采用EventBus代碼的可讀性也會變差。
前言Android進程和Service的保活,是困擾Android開發人員的一大頑疾。因涉及到省電和內存管理策略,各廠商基於自家的理解,在自已ROOM發布於都對標准And
Drawable animation可以加載Drawable資源實現幀動畫。AnimationDrawable是實現Drawable animations的基本類。&nb
跟著官方教程學習數據綁定的用法,功能確實非常強大,這是 Android 向 MVVM 邁出的一大步,也是 Native 的開發方式逐漸向 Web 靠攏的一小步。其中一個綁
之前學習過了MediaPlayer用於播放手機音樂,但是在手機中很多的提示音並不是使用MediaPlayer來播放的比如短信鈴聲,通知鈴聲,android中使用Sound