##寫在前面java
###1.前言 曾經,一層又一層的業務邏輯讓我不知所措,一個又一個的回調讓你頭暈眼花,一個又一個的參數讓你混亂不堪。EventBus,,一個耦合度低到令你懼怕的框架。 ###2.什麼是EventBus EventBus是一個消息總線,以觀察者模式實現,用於簡化程序的組件、線程通訊,能夠輕易切換線程、開闢線程。EventBus3.0跟先前版本的區別在於加入了annotation @Subscribe,取代了之前約定命名的方式。 ###3.類似產品比較android
產品名 | 開發者 | 備註 |
---|---|---|
EventBus | greenrobot | 用戶最多,簡潔,方便,小巧,文檔簡潔明瞭 |
Guava | 一個龐大的工具類庫,EventBus只是一個小功能 | |
otto | square | fork guava ,用的人很多 |
AndroidEventBus | 何紅輝 | 模仿EventBus開發的 |
##使用EventBus3.0三部曲git
###1.定義事件github
public class MessageEvent { public final String message; public MessageEvent(String message) { this.message = message; } }
###2.準備訂閱者segmentfault
// This method will be called when a MessageEvent is posted @Subscribe public void onMessageEvent(MessageEvent event){ Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show(); } // This method will be called when a SomeOtherEvent is posted @Subscribe public void handleSomethingElse(SomeOtherEvent event){ doSomethingWith(event); }
@Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { EventBus.getDefault().unregister(this); super.onStop(); }
###3.發送事件網絡
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
##深刻了解 ###1.ThreadMode線程通訊 EventBus能夠很簡單的實現線程間的切換,包括後臺線程、UI線程、異步線程 ####ThreadMode.POSTING併發
//默認調用方式,在調用post方法的線程執行,避免了線程切換,性能開銷最少 // Called in the same thread (default) @Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional here public void onMessage(MessageEvent event) { log(event.message); }
####ThreadMode.MAINapp
// Called in Android UI's main thread @Subscribe(threadMode = ThreadMode.MAIN) public void onMessage(MessageEvent event) { textField.setText(event.message); }
####ThreadMode.BACKGROUND框架
// 若是調用post方法的線程不是主線程,則直接在該線程執行 // 若是是主線程,則切換到後臺單例線程,多個方法公用同個後臺線程,按順序執行,避免耗時操做 // Called in the background thread @Subscribe(threadMode = ThreadMode.BACKGROUND) public void onMessage(MessageEvent event){ saveToDisk(event.message); }
####ThreadMode.ASYNC異步
//開闢新獨立線程,用來執行耗時操做,例如網絡訪問 //EventBus內部使用了線程池,可是要儘可能避免大量長時間運行的異步線程,限制併發線程數量 //能夠經過EventBusBuilder修改,默認使用Executors.newCachedThreadPool() // Called in a separate thread @Subscribe(threadMode = ThreadMode.ASYNC) public void onMessage(MessageEvent event){ backend.send(event.message); }
###2.配置EventBusBuilder EventBus提供了不少配置,通常的狀況下咱們能夠不用配置.可是,若是你有一些其餘要求,好比控制日誌在開發的時候輸出,發佈的時候不輸出,在開發的時候錯誤崩潰,而發佈的時候不崩潰...等狀況。 EventBus提供了一個默認的實現,但不是單例。
EventBus eventBus = new EventBus(); //下面這一條的效果是徹底同樣的 EventBus eventBus = EventBus.builder().build(); //修改默認實現的配置,記住,必須在第一次EventBus.getDefault()以前配置,且只能設置一次。建議在application.onCreate()調用 EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();
###3.StickyEvent StickyEvent在內存中保存最新的消息,取消原有消息,執行最新消息,只有在註冊後纔會執行,若是沒有註冊,消息會一直保留來內存中
//在註冊以前發送消息 EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
//限制,新界面啓動了 @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } //在onStart調用register後,執行消息 @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEvent(MessageEvent event) { // UI updates must run on MainThread textField.setText(event.message); } @Override public void onStop() { EventBus.getDefault().unregister(this); super.onStop(); }
你也能夠手動管理StickyEvent
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class); // Better check that an event was actually posted before if(stickyEvent != null) { // "Consume" the sticky event EventBus.getDefault().removeStickyEvent(stickyEvent); //or EventBus.getDefault().removeAllStickyEvents(); // Now do something with it }
在這裏,或許你會有個疑問, StickyEvent=true的訂閱者可否接收post的事件? StickyEvent=false的訂閱者可否接收postSticky的事件? 查看源碼發現
/** * 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 event's 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); }
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { ...省略部分代碼 if (subscriberMethod.sticky) { 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); } } }
boolean checkAdd(Method method, Class<?> eventType) { // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required. // Usually a subscriber doesn't have methods listening to the same event type. Object existing = anyMethodByEventType.put(eventType, method); if (existing == null) { return true; } else { if (existing instanceof Method) { if (!checkAddWithMethodSignature((Method) existing, eventType)) { // Paranoia check throw new IllegalStateException(); } // Put any non-Method object to "consume" the existing Method anyMethodByEventType.put(eventType, this); } return checkAddWithMethodSignature(method, eventType); } }
發現,post方法沒有過濾StickyEvent,而postSticky是調用post方法的,因此,不管post仍是postSticky,StickyEvent是true或false,都會執行 ###4.priority事件優先級
//priority越大,級別越高 @Subscribe(priority = 1); public void onEvent(MessageEvent event) { … }
//優先級實現方式,遍歷當前列表,把當前 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; } }
###5.停止事件傳遞
// 停止事件傳遞,後續事件不在調用,注意,只能在傳遞事件的時候調用 @Subscribe public void onEvent(MessageEvent event){ … EventBus.getDefault().cancelEventDelivery(event) ; }
###6.index索引加速 EventBus使用了annotation,默認在編譯時生成代碼,生成索引, 添加index後會在編譯時運行,自動生成相應代碼。 ps:因爲apt的限制,匿名內部類中的annotation不會被識別,會自動降級在運行時反射,此時,效率會下降
buildscript { dependencies { classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } apply plugin: 'com.neenbedankt.android-apt' dependencies { compile 'org.greenrobot:eventbus:3.0.0' apt 'org.greenrobot:eventbus-annotation-processor:3.0.1' } apt { arguments { eventBusIndex "com.example.myapp.MyEventBusIndex" } }
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus(); // Now the default instance uses the given index. Use it like this: EventBus eventBus = EventBus.getDefault();
###7.NoSubscriberEvent 若是沒找到訂閱者事件,能夠經過EventBusBuilder設置是否默認發送NoSubscriberEvent,默認是打開的
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { .... 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)); } } }
###8.混淆
-keepattributes *Annotation* -keepclassmembers class ** { @org.greenrobot.eventbus.Subscribe <methods>; } -keep enum org.greenrobot.eventbus.ThreadMode { *; } # Only required if you use AsyncExecutor -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { <init>(java.lang.Throwable); }
###9.利弊 好處
簡單,方便,小巧,文檔清晰,性能消耗少,可定製行強,耦合度低
壞處
耦合度過低
這絕對不是個笑話,,EventBus的耦合過低了,若是不加以控制管理,你會不知道,你發的消息到跑哪裏去了。也不知道你的這條消息,會在哪裏發出。若是你沒有很好的方法解決這個問題,建議很差用太多。
##使用建議
###一、EventBus管理 EventBus運行建立多個,那麼,明確事件的生命週期,根據不一樣生命週期使用不一樣的EventBus?
/** * 方法1 * 用annotation配合使用工廠 * EventBusFactory.getBus(EventBusFactory.START); * EventBusFactory.getBus(); */ public class EventBusFactory { private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2); @IntDef({CREATE, START}) @Retention(RetentionPolicy.SOURCE) public @interface BusType { } public static final int CREATE = 0; public static final int START = 1; static { mBusSparseArray.put(CREATE, EventBus.builder().build()); mBusSparseArray.put(START, EventBus.getDefault()); } public static EventBus getBus() { return getBus(START); } public static EventBus getBus(@BusType int type) { return mBusSparseArray.get(type); } }
/** * 方法2 * 用枚舉工廠 * EventBusFactory.START.getBus(); */ public enum EventBusFactory { CREATE(0), START(1); private int mType; EventBusFactory(int type) { mType = type; } public EventBus getBus() { return mBusSparseArray.get(mType); } private static SparseArray<EventBus> mBusSparseArray = new SparseArray<>(2); static { mBusSparseArray.put(CREATE.mType, EventBus.builder().build()); mBusSparseArray.put(START.mType, EventBus.getDefault()); } }
###二、以事件爲對象 將數據封裝到一個事件類。全部事件放到一個包下。若是事件太多,同個模塊的事件能夠考慮使用靜態內部類,或者再分包。
/** * This Event is posted by EventBus when no subscriber is found for a posted event. * * @author Markus */ public final class NoSubscriberEvent { /** The {@link EventBus} instance to with the original event was posted to. */ public final EventBus eventBus; /** The original event that could not be delivered to any subscriber. */ public final Object originalEvent; public NoSubscriberEvent(EventBus eventBus, Object originalEvent) { this.eventBus = eventBus; this.originalEvent = originalEvent; } }
public class Event { public static class UserListEvent { public List<User> users ; } public static class ItemListEvent { public List<Item> items; } }
注意,不是相同類型就必定要做爲一個事件封裝,具體須要考慮業務情景跟代碼狀況,好比事件行爲不一樣、事件生命週期不一樣,若是有必要,寫封裝成兩個Event多是更好的選擇。
public class Event { public static class UserListUpdateEventOnCreate { public List<User> users; } public static class UserListUpdateEventOnStart { public List<User> users ; } public static class UserListRemoveEventOnStart { public List<User> users; } }
##參考文獻
##說了這麼多廢話,,下面進入正題