本篇開始,則轉向
greenrobot/EventBus
, 以前基本上將Guava中設計的思路捋了一遍,邏輯比較簡單和清晰,接下來則看下普遍運用於android的這個框架又有什麼不同的地方,有什麼獨特的精妙所在java
開始以前,固然是要先把代碼donw下來,而後本機能跑起來才行; so,基本的環境要搞起, Android Studio
將做爲主要的ideandroid
在導入工程以後,發現一直報一個 jdk版本太低的異常,解決方法是設置ide的jdk環境,以下,指定jdk爲8就能夠了數據結構
在開始以前,先看下這個框架怎麼用,會用了以後才能更好的考慮怎麼去分析拆解app
用法相比較Guava EventBus
差異不大, 除了支持註解方式以外,還支持非註解形式,如框架
public class EventBusIndexTest { private String value; @Test /** Ensures the index is actually used and no reflection fall-back kicks in. */ public void testManualIndexWithoutAnnotation() { SubscriberInfoIndex index = new SubscriberInfoIndex() { @Override public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) { Assert.assertEquals(EventBusIndexTest.class, subscriberClass); SubscriberMethodInfo[] methodInfos = { new SubscriberMethodInfo("someMethodWithoutAnnotation", String.class) }; return new SimpleSubscriberInfo(EventBusIndexTest.class, false, methodInfos); } }; EventBus eventBus = EventBus.builder().addIndex(index).build(); eventBus.register(this); eventBus.post("Yepp"); eventBus.unregister(this); Assert.assertEquals("Yepp", value); } public void someMethodWithoutAnnotation(String value) { this.value = value; } }
上面與咱們以前的使用,區別主要在於 EventBus
對象的獲取,down下來的工程中,有一個基礎的測試類,給咱們演示了很多的使用方式異步
@RunWith(AndroidJUnit4.class) public class EventBusBasicTest { public static class WithIndex extends EventBusBasicTest { @Test public void dummy() {} } @Rule public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); protected EventBus eventBus; private String lastStringEvent; private int countStringEvent; private int countIntEvent; private int lastIntEvent; private int countMyEventExtended; private int countMyEvent; private int countMyEvent2; @Before public void setUp() throws Exception { eventBus = new EventBus(); } @Test @UiThreadTest public void testRegisterAndPost() { // Use an activity to test real life performance TestActivity testActivity = new TestActivity(); String event = "Hello"; long start = System.currentTimeMillis(); eventBus.register(testActivity); long time = System.currentTimeMillis() - start; Log.d(EventBus.TAG, "Registered in " + time + "ms"); eventBus.post(event); assertEquals(event, testActivity.lastStringEvent); } // 無訂閱者 @Test public void testPostWithoutSubscriber() { eventBus.post("Hello"); } @Test public void testUnregisterWithoutRegister() { // Results in a warning without throwing eventBus.unregister(this); } // This will throw "out of memory" if subscribers are leaked @Test public void testUnregisterNotLeaking() { int heapMBytes = (int) (Runtime.getRuntime().maxMemory() / (1024L * 1024L)); for (int i = 0; i < heapMBytes * 2; i++) { EventBusBasicTest subscriber = new EventBusBasicTest() { byte[] expensiveObject = new byte[1024 * 1024]; }; eventBus.register(subscriber); eventBus.unregister(subscriber); Log.d("Test", "Iteration " + i + " / max heap: " + heapMBytes); } } @Test public void testRegisterTwice() { eventBus.register(this); try { eventBus.register(this); fail("Did not throw"); } catch (RuntimeException expected) { // OK } } @Test public void testIsRegistered() { assertFalse(eventBus.isRegistered(this)); eventBus.register(this); assertTrue(eventBus.isRegistered(this)); eventBus.unregister(this); assertFalse(eventBus.isRegistered(this)); } @Test public void testPostWithTwoSubscriber() { EventBusBasicTest test2 = new EventBusBasicTest(); eventBus.register(this); eventBus.register(test2); String event = "Hello"; eventBus.post(event); assertEquals(event, lastStringEvent); assertEquals(event, test2.lastStringEvent); } @Test public void testPostMultipleTimes() { eventBus.register(this); MyEvent event = new MyEvent(); int count = 1000; long start = System.currentTimeMillis(); // Debug.startMethodTracing("testPostMultipleTimes" + count); for (int i = 0; i < count; i++) { eventBus.post(event); } // Debug.stopMethodTracing(); long time = System.currentTimeMillis() - start; Log.d(EventBus.TAG, "Posted " + count + " events in " + time + "ms"); assertEquals(count, countMyEvent); } @Test public void testMultipleSubscribeMethodsForEvent() { eventBus.register(this); MyEvent event = new MyEvent(); eventBus.post(event); assertEquals(1, countMyEvent); assertEquals(1, countMyEvent2); } @Test public void testPostAfterUnregister() { eventBus.register(this); eventBus.unregister(this); eventBus.post("Hello"); assertNull(lastStringEvent); } @Test public void testRegisterAndPostTwoTypes() { eventBus.register(this); eventBus.post(42); eventBus.post("Hello"); assertEquals(1, countIntEvent); assertEquals(1, countStringEvent); assertEquals(42, lastIntEvent); assertEquals("Hello", lastStringEvent); } @Test public void testRegisterUnregisterAndPostTwoTypes() { eventBus.register(this); eventBus.unregister(this); eventBus.post(42); eventBus.post("Hello"); assertEquals(0, countIntEvent); assertEquals(0, lastIntEvent); assertEquals(0, countStringEvent); } @Test public void testPostOnDifferentEventBus() { eventBus.register(this); new EventBus().post("Hello"); assertEquals(0, countStringEvent); } @Test public void testPostInEventHandler() { RepostInteger reposter = new RepostInteger(); eventBus.register(reposter); eventBus.register(this); eventBus.post(1); assertEquals(10, countIntEvent); assertEquals(10, lastIntEvent); assertEquals(10, reposter.countEvent); assertEquals(10, reposter.lastEvent); } @Test public void testHasSubscriberForEvent() { assertFalse(eventBus.hasSubscriberForEvent(String.class)); eventBus.register(this); assertTrue(eventBus.hasSubscriberForEvent(String.class)); eventBus.unregister(this); assertFalse(eventBus.hasSubscriberForEvent(String.class)); } @Test public void testHasSubscriberForEventSuperclass() { assertFalse(eventBus.hasSubscriberForEvent(String.class)); Object subscriber = new ObjectSubscriber(); eventBus.register(subscriber); assertTrue(eventBus.hasSubscriberForEvent(String.class)); eventBus.unregister(subscriber); assertFalse(eventBus.hasSubscriberForEvent(String.class)); } @Test public void testHasSubscriberForEventImplementedInterface() { assertFalse(eventBus.hasSubscriberForEvent(String.class)); Object subscriber = new CharSequenceSubscriber(); eventBus.register(subscriber); assertTrue(eventBus.hasSubscriberForEvent(CharSequence.class)); assertTrue(eventBus.hasSubscriberForEvent(String.class)); eventBus.unregister(subscriber); assertFalse(eventBus.hasSubscriberForEvent(CharSequence.class)); assertFalse(eventBus.hasSubscriberForEvent(String.class)); } @Subscribe public void onEvent(String event) { lastStringEvent = event; countStringEvent++; } @Subscribe public void onEvent(Integer event) { lastIntEvent = event; countIntEvent++; } @Subscribe public void onEvent(MyEvent event) { countMyEvent++; } @Subscribe public void onEvent2(MyEvent event) { countMyEvent2++; } @Subscribe public void onEvent(MyEventExtended event) { countMyEventExtended++; } public static class TestActivity extends Activity { public String lastStringEvent; @Subscribe public void onEvent(String event) { lastStringEvent = event; } } public static class CharSequenceSubscriber { @Subscribe public void onEvent(CharSequence event) { } } public static class ObjectSubscriber { @Subscribe public void onEvent(Object event) { } } public class MyEvent { } public class MyEventExtended extends MyEvent { } public class RepostInteger { public int lastEvent; public int countEvent; @Subscribe public void onEvent(Integer event) { lastEvent = event; countEvent++; assertEquals(countEvent, event.intValue()); if (event < 10) { int countIntEventBefore = countEvent; eventBus.post(event + 1); // All our post calls will just enqueue the event, so check count is unchanged assertEquals(countIntEventBefore, countIntEventBefore); } } } }
提供了三中建立方式,一個是直接使用默認實例; 一個最簡單普通的構造;再者使用 EventBusBuilder
來構建, 下面會分別對上面的幾種狀況進行分析說明async
EventBus.getDefault()
默認實例這裏使用了最多見的延遲加載的單例模式,來獲取實例,注意下 snchronized
的使用位置,並無放在方法簽名上( 注意這個類不是嚴格意義上的單例,由於構造函數是public)ide
static volatile EventBus defaultInstance; /** 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; }
此外另外一種常見的單例模式下面也順手貼出,主要利用靜態內部類函數
private static class InnerInstance { static volatile EventBus defaultInstance = new EventBus(); } public static EventBus getInstance() { return InnerInstance.defaultInstance; }
new EventBus()
構造器這個比較簡單了,基本上獲取實例的方法都這麼玩,對於EventBus而言,把這個放開的一個關鍵點是再你選的系統中,不會限制你的EventBus
實例個數,也就是說,你的系統能夠有多個並行的消息-事務總線工具
這個模式也是比較經常使用的,對於構建複雜對象時,選擇的Builder模式,對這個的分析以前,先看下 EventBus
的屬性
static volatile EventBus defaultInstance; private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder(); private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>(); private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; private final Map<Object, List<Class<?>>> typesBySubscriber; private final Map<Class<?>, Object> stickyEvents; private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); } }; private final HandlerPoster mainThreadPoster; private final BackgroundPoster backgroundPoster; private final AsyncPoster asyncPoster; private final SubscriberMethodFinder subscriberMethodFinder; private final ExecutorService executorService; private final boolean throwSubscriberException; private final boolean logSubscriberExceptions; private final boolean logNoSubscriberMessages; private final boolean sendSubscriberExceptionEvent; private final boolean sendNoSubscriberEvent; private final boolean eventInheritance;
SubscriberMethod
訂閱者回調方法封裝類這個類主要保存的就是訂閱者的回調方法相關信息
final Method method; final ThreadMode threadMode; // 監聽的事件類型, 也就是註冊方法的惟一參數類型 final Class<?> eventType; // 定義監聽消息的優先級 final int priority; //在Android開 發中,Sticky事件只指事件消費者在事件發佈以後才註冊的也能接收到該事件的特殊類型 final boolean sticky; /** Used for efficient comparison */ String methodString;
ThreadMode, 區分了如下幾種類型,且各自的意思以下
/** * 和發佈消息的公用一個線程 * Subscriber will be called in the same thread, which is posting the event. This is the default. Event delivery * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for * simple tasks that are known to complete is a very short time without requiring the main thread. Event handlers * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread. */ POSTING, /** * 再android的主線程下 * Subscriber will be called in Android's main thread (sometimes referred to as UI thread). If the posting thread is * the main thread, event handler methods will be called directly. Event handlers using this mode must return * quickly to avoid blocking the main thread. */ MAIN, /** * Subscriber will be called in a background thread. If posting thread is not the main thread, event handler methods * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single * background thread, that will deliver all its events sequentially. Event handlers using this mode should try to * return quickly to avoid blocking the background thread. */ BACKGROUND, /** * Event handler methods are called in a separate thread. This is always independent from the posting thread and the * main thread. Posting events never wait for event handler methods using this mode. Event handler methods should * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number * of long running asynchronous handler methods at the same time to limit the number of concurrent threads. EventBus * uses a thread pool to efficiently reuse threads from completed asynchronous event handler notifications. */ ASYNC
Subscription
註冊回調信息封裝類EventBus中維護的訂閱關係, 在對象註冊時注入到
EventBus
, 推送消息時,則會從EventBus
中獲取
EventBus
中維護的訂閱者關係數據結構就是 Map<Class<?>, CopyOnWriteArrayList<Subscription>>
, 其中key爲事件類型, value就是訂閱者信息集合
final Object subscriber; final SubscriberMethod subscriberMethod;
Subscribe
註解註解標註在類的惟一參數的公共方法上, 表示這個方法就是咱們註冊的訂閱者回調方法
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Subscribe { ThreadMode threadMode() default ThreadMode.POSTING; /** * If true, delivers the most recent sticky event (posted with * {@link EventBus#postSticky(Object)}) to this subscriber (if event available). */ boolean sticky() default false; /** Subscriber priority to influence the order of event delivery. * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of * delivery among subscribers with different {@link ThreadMode}s! */ int priority() default 0; }
SubscriberMethodFinder
輔助工具類,獲取訂閱者中的回調方法顧名思義,這個就是用來獲取訂閱者類中的全部註冊方法的,支持兩種方式,一個是上面的註解方式;還有一個則是利用
SubscriberInfo
核心方法:List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)
SubscriberInfo
定義獲取訂閱者註冊方法的接口一般這個會和SubscriberInfoIndex
配合使用,後面這個接口專一返回 SubscriberInfo
對象,其默認的實現也比較簡單,基本上就是指定完整的註冊方法信息(SubscriberMethodInfo
)便可
public class SubscriberMethodInfo { final String methodName; final ThreadMode threadMode; final Class<?> eventType; final int priority; final boolean sticky; } public class SimpleSubscriberInfo implements SubscriberInfo { public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass,SubscriberMethodInfo[] methodInfos) { this.subscriberClass = subscriberClass; this.superSubscriberInfoClass = null; this.shouldCheckSuperclass = shouldCheckSuperclass; this.methodInfos = methodInfos; } }
拿一個異步的例子看一下, 裏面包含兩個對象, 一個 queue
消息推送的隊列,一個 eventBus
實例,消息推送,則是調用 enqueue()
方法, 先將監聽者 + 消息塞入隊列, 而後調用 eventBus的線程池實進行實現異步的消息推送
private final PendingPostQueue queue; private final EventBus eventBus; AsyncPoster(EventBus eventBus) { this.eventBus = eventBus; queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); eventBus.getExecutorService().execute(this); } @Override public void run() { PendingPost pendingPost = queue.poll(); if(pendingPost == null) { throw new IllegalStateException("No pending post available"); } eventBus.invokeSubscriber(pendingPost); }