概述
- EventBus是Android和Java的發布/訂閱事件總線。
- 簡化了組件之間的通信;
- 將事件發送者和接收者分開;
- 與Activity,Fragment和后臺線程之間調度非常好;
- 避免了復雜和容易出錯的依賴和生命周期問題;
- 讓你的代碼變得更簡單易懂;
常用的事件傳遞
- Intent 意圖,跳轉+傳參;
- Handler ,通常用來在主線程更新UI,使用不當容易出現內存泄漏;
- Interface接口,僅限于統一在同一線程中數據交互;
- BoardCastReceiver ,有序+ 無序廣播,不安全或者容易ANR;
- AIDL 跨進程通訊,代碼閱讀星不好,維護成本高;
- 其他方式,本地存儲;
如何使用
EventBus 4 部曲(官方3步):
1、引入依賴:
Grdle:
compile 'org.greenrobot:eventbus:3.1.1'
如果你的項目需要混淆記得加入:
-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);
}
2、定義一個消息類,該類不繼承任何基類也不要實現任何接口。如:
public class MessageEvent
{
public String message;
public MessageEvent(String message){
this.message = message;
}
}
3、定義事件回調方法,threadMode是可選:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
4、在需要訂閱事件的地方注冊事件(必須要先注冊,不然無法收到消息):
EventBus.getDefault().register(this);
5、發送消息
EventBus .getDefault().post(new MessageEvent());
6、處理消息,即接受到MessageEvent消息做出反應:
@Subscribe(threadMode = ThreadMode.PostThread)
public void XXX(MessageEvent messageEvent) {
...
}
在3.0之前,EventBus還沒有使用注解方式。消息處理的方法也只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分別代表四種線程模型。而在3.0之后,消息處理的方法可以隨便取名,但是需要添加一個注解@Subscribe,并且要指定線程模型(默認為PostThread),四種線程模型,下面會講到。
注意,事件處理函數的訪問權限必須為public,否則會報異常。
7、取消消息事件的訂閱:
EventBus.getDefault().unregister(this);
EventBus有何優點
采用消息發布/訂閱的一個很大的優點就是代碼的簡潔性,并且能夠有效地降低消息發布者和訂閱者之間的耦合度;
舉個例子,比如有兩個界面,ActivityA和ActivityB,從ActivityA界面跳轉到ActivityB界面后,ActivityB要給ActivityA發送一個消息,ActivityA收到消息后在界面上顯示出來,我可以告訴你,這樣也可以,就是代碼過于臃腫;
常用API介紹
線程模型
在EventBus的事件處理函數中需要指定線程模型,即指定事件處理函數運行所在的想線程。在上面我們已經接觸到了EventBus的四種線程模型。那他們有什么區別呢?
在EventBus中的觀察者通常有四種線程模型,分別是PostThread(默認)、MainThread、BackgroundThread與Async。
PostThread:如果使用事件處理函數指定了線程模型為PostThread,那么該事件在哪個線程發布出來的,事件處理函數就會在這個線程中運行,也就是說發布事件和接收事件在同一個線程。在線程模型為PostThread的事件處理函數中盡量避免執行耗時操作,因為它會阻塞事件的傳遞,甚至有可能會引起ANR。
MainThread:如果使用事件處理函數指定了線程模型為MainThread,那么不論事件是在哪個線程中發布出來的,該事件處理函數都會在UI線程中執行。該方法可以用來更新UI,但是不能處理耗時操作。
BackgroundThread:如果使用事件處理函數指定了線程模型為
-
BackgroundThread,那么如果事件是在UI線程中發布出來的,那么該事件處理函數就會在新的線程中運行,如果事件本來就是子線程中發布出來的,那么該事件處理函數直接在發布事件的線程中執行。在此事件處理函數中禁止進行UI更新操作。
Async:如果使用事件處理函數指定了線程模型為Async,那么無論事件在哪個線程發布,該事件處理函數都會在新建的子線程中執行。同樣,此事件處理函數中禁止進行UI更新操作。@Subscribe(threadMode = ThreadMode.PostThread) public void onMessageEventPostThread(MessageEvent messageEvent) { Log.e("FY", Thread.currentThread().getName()); } @Subscribe(threadMode = ThreadMode.MainThread) public void onMessageEventMainThread(MessageEvent messageEvent) { Log.e("FY", Thread.currentThread().getName()); } @Subscribe(threadMode = ThreadMode.BackgroundThread) public void onMessageEventBackgroundThread(MessageEvent messageEvent) { Log.e("FY", Thread.currentThread().getName()); } @Subscribe(threadMode = ThreadMode.Async) public void onMessageEventAsync(MessageEvent messageEvent) { Log.e("FY", Thread.currentThread().getName()); }
分別使用上面四個方法訂閱同一事件,打印他們運行所在的線程。首先我們在UI線程中發布一條MessageEvent的消息,看下日志打印結果是什么。
打印結果如下:
postEvent﹕ main
PostThread﹕ main
Async﹕ pool-1-thread-1
MainThread﹕ main
BackgroundThread﹕ pool-1-thread-2
從日志打印結果可以看出,如果在UI線程中發布事件,則線程模型為PostThread的事件處理函數也執行在UI線程,與發布事件的線程一致。線程模型為Async的事件處理函數執行在名字叫做pool-1-thread-1的新的線程中。而MainThread的事件處理函數執行在UI線程,BackgroundThread的時間處理函數執行在名字叫做pool-1-thread-2的新的線程中。
再看看在子線程中發布一條MessageEvent的消息時,會有什么樣的結果。
打印結果如下:
postEvent﹕ Thread-1
PostThread﹕ Thread-1
BackgroundThread﹕ Thread-1
Async﹕ pool-1-thread-1
MainThread﹕ main
從日志打印結果可以看出,如果在子線程中發布事件,則線程模型為PostThread的事件處理函數也執行在子線程,與發布事件的線程一致(都是Thread-1)。BackgroundThread事件模型也與發布事件在同一線程執行。Async則在一個名叫pool-1-thread-1的新線程中執行。MainThread還是在UI線程中執行。
黏性事件
除了上面講的普通事件外,EventBus還支持發送黏性事件。臥槽,黏性事件?簡單講,就是在發送事件之后再訂閱該事件也能收到該事件,一些事件在事件發布后攜帶有興趣的信息。例如,一個事件表示一些初始化完成。或者如果您有一些傳感器或位置數據,并且想要保留最新的值。而不是實現自己的緩存,你可以使用粘性事件。所以EventBus將最后一個特定類型的粘滯事件保存在內存中。然后粘性事件可以傳遞給訂閱者或者顯式查詢。因此,您不需要任何特殊的邏輯來考慮已有的數據。跟黏性廣播類似。具體用法如下:
訂閱黏性事件:
EventBus.getDefault().register(StickyModeActivity.this);
黏性事件處理函數:
@Subscribe(sticky = true)
public void XXX(MessageEvent messageEvent) {
......
}
發送黏性事件:
EventBus.getDefault().postSticky(new MessageEvent("test"));
手動獲取和刪除粘性事件
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);
// Now do something with it
}
removeStickyEvent方法被重載:當你傳入類時,它將返回以前持有的粘性事件。使用這個變體,我們可以改進前面的例子:
MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
// Now do something with it
}
處理消息事件以及取消訂閱和上面方式相同。
建議看官方文檔
開始造輪子
看過EventBus的源碼應該知道,EventBus的實現使用了觀察者模式。代碼如下:
public void register(Object object) {
List<SubscriberMethod> subscribes = mCacheMap.get(object);
if (subscribes == null) {
synchronized (SimpleEventBus.class) {
subscribes = findSubscribeMethod(object);
mCacheMap.put(object, subscribes);
}
}
}
private List<SubscriberMethod> findSubscribeMethod(Object object) {
List<SubscriberMethod> subscriberMethods = new CopyOnWriteArrayList<>();
Class<?> clazz = object.getClass();
while (clazz != null) {
String name = clazz.getName();
//排除系統的類或接口,不能是系統的類或接口,因為我們的訂閱方法只能是我們自己的類或接口
if (name.startsWith("java") || name.startsWith("javax") || name.startsWith("android")) {
break;
}
//該類定義的Method
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation annotation = method.getAnnotation(Subscribe.class);
if (annotation == null) continue;
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) throw new RuntimeException("只能有一個參數");
Class<?> methodParameterType = parameterTypes[0];
ThreadMode threadMode = ((Subscribe) annotation).threadMode();
subscriberMethods.add(new SubscriberMethod(method, threadMode, methodParameterType));
}
clazz = clazz.getSuperclass();
}
if (subscriberMethods.size() <= 0) {
throw new RuntimeException("必須有一個訂閱方法");
}
return subscriberMethods;
}
看到當EventBus開始register的時候,通過解析Class中被標注@Sunscriber注解的方法Method,拿到注解標識的接收事件的方法,然后解析注解并將解析到的Method和Method Param type以及ThreadMode保存到內存中,在來看看那post方法,代碼如下:
/**
* 實際上是根據訂閱方法的參數和發布傳進來的對象進行對比
*
* @param eventMessage
*/
public void post(Object eventMessage) {
Set<Object> objects = mCacheMap.keySet();
for (Object obj : objects) {
List<SubscriberMethod> subscriberMethods = mCacheMap.get(obj);
if (subscriberMethods == null) continue;
for (SubscriberMethod subMethod : subscriberMethods) {
Class<?> aClass = subMethod.getEventType();
Class<?> bClass = eventMessage.getClass();
//aClass 的class是是不是bClass的的父類或者接口
if (aClass.isAssignableFrom(bClass)) {
invoke(subMethod, obj, eventMessage);
}
}
}
}
private void invoke(SubscriberMethod subscriberMethod, Object obj, final Object eventObj) {
EventTask eventTask = new EventTask(subscriberMethod.getMethod(), obj, eventObj, subscriberMethod.getThreadMode());
ScheduleRouterExecutor.getInstance().executeTask(eventTask);
}
當post的時候,通過post傳入的參數類型
與第2步解析的得到的方法參數類型進行對比,回調的對應的方法,整個過程就結束了。
總結:
1、因為EventBus 3.0之前,在運行時大量存在了反射,勢必會造成不必要的性能問題,目前官方做了優化,使用了編譯時注解,也就是index,如何使用Eventbus注解。
2、EventBus并不支持跨進程通訊,我們是否可以對他進程拓展?那么這樣會不會和第1的問題沖突呢?
從上面知道EventBus是不支持跨進程通訊和大量反射有性能損耗,后期將嘗試解決這些問題demo地址