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