EventBus3.0詳解

##寫在前面java

###1.前言 曾經,一層又一層的業務邏輯讓我不知所措,一個又一個的回調讓你頭暈眼花,一個又一個的參數讓你混亂不堪。EventBus,,一個耦合度低到令你懼怕的框架。 ###2.什麼是EventBus EventBus是一個消息總線,以觀察者模式實現,用於簡化程序的組件、線程通訊,能夠輕易切換線程、開闢線程。EventBus3.0跟先前版本的區別在於加入了annotation @Subscribe,取代了之前約定命名的方式。 ###3.類似產品比較android

產品名 開發者 備註
EventBus greenrobot 用戶最多,簡潔,方便,小巧,文檔簡潔明瞭
Guava google 一個龐大的工具類庫,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;  
    } 
}

##參考文獻

  1. EventBus官網地址
  2. EventBus github地址
  3. EventBus 3.0的用法詳解

##說了這麼多廢話,,下面進入正題

福利

相關文章
相關標籤/搜索