1. 程式人生 > >Android——EventBus3.0的使用詳解

Android——EventBus3.0的使用詳解

什麼是EventBus

EventBus是一個訊息匯流排,以觀察者模式實現,用於簡化程式的元件、執行緒通訊,可以輕易切換執行緒、開闢執行緒。EventBus3.0跟先前版本的區別在於加入了annotation @Subscribe,取代了以前約定命名的方式。

如何使用EventBus

1.定義事件

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

2.準備訂閱者

// 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!"
));

EventBus詳解

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.MAIN

// 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的耦合太低了,如果不加以控制管理,你會不知道,你發的訊息到跑哪裡去了。也不知道你的這條訊息,會在哪裡發出。如果你沒有很好的方法解決這個問題,建議不好用太多。

10.使用建議

1、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());
    }
}

2、以事件為物件

將資料封裝到一個事件類。所有事件放到一個包下。如果事件太多,同個模組的事件可以考慮使用靜態內部類,或者再分包。

/**
 * 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;  
    } 
}

參考文獻