EventBus的使用之重複早輪子

概述

  • EventBus是Android和Java的發佈/訂閱事件總線。
  • 簡化了組件之間的通訊;
    • 將事件發送者和接收者分開;
    • 與Activity,Fragment和後臺線程之間調度很是好;
    • 避免了複雜和容易出錯的依賴和生命週期問題;
  • 讓你的代碼變得更簡單易懂;

如何使用

EventBus 4 部曲(官方3步): 一、引入依賴:java

Grdle:
compile 'org.greenrobot:eventbus:3.1.1'
複製代碼

若是你的項目須要混淆記得加入:android

-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);
}
複製代碼

二、定義一個消息類,該類不繼承任何基類也不要實現任何接口。如:git

public  class MessageEvent 
{  
    public String message;
    public MessageEvent(String message){
                this.message = message;
      }
}
複製代碼

三、定義事件回調方法,threadMode是可選:github

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};
複製代碼

四、在須要訂閱事件的地方註冊事件(必需要先註冊,否則沒法收到消息):緩存

EventBus.getDefault().register(this);
複製代碼

五、發送消息函數

EventBus .getDefault().post(new  MessageEvent());  
複製代碼

六、處理消息,即接受到MessageEvent消息作出反應:post

@Subscribe(threadMode = ThreadMode.PostThread)
public void XXX(MessageEvent messageEvent) {
    ...
}
複製代碼

在3.0以前,EventBus尚未使用註解方式。消息處理的方法也只能限定於onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分別表明四種線程模型。而在3.0以後,消息處理的方法能夠隨便取名,可是須要添加一個註解@Subscribe,而且要指定線程模型(默認爲PostThread),四種線程模型,下面會講到。 注意,事件處理函數的訪問權限必須爲public,不然會報異常。 七、取消消息訂閱:性能

EventBus.getDefault().unregister(this);
複製代碼

#EventBus有何優勢 採用消息發佈/訂閱的一個很大的優勢就是代碼的簡潔性,而且可以有效地下降消息發佈者和訂閱者之間的耦合度; 舉個例子,好比有兩個界面,ActivityA和ActivityB,從ActivityA界面跳轉到ActivityB界面後,ActivityB要給ActivityA發送一個消息,ActivityA收到消息後在界面上顯示出來,我能夠告訴你,這樣也能夠,就是代碼過於臃腫;ui

#經常使用API介紹this

線程模型

在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的時候經過解析註解,拿到註解標識的接收事件的方法,而後解析註解並將解析到的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步解析的獲得的數據進行對比,回調的對應的方法,整個過程就結束了。

#####最後兩個問題: 一、由於EventBus,在運行時大量存在了反射,勢必會形成沒必要要的性能問題,咱們是否可使用編譯註解解決這個問題,相似於ARouter的方式? 二、EventBus並不支持跨進程通信,咱們是否能夠對他進程拓展?那麼這樣會不會和第1的問題衝突呢?

從上面知道EventBus是不支持跨進程通信和大量反射有性能損耗,後期將嘗試解決這些問題demo地址

相關文章
相關標籤/搜索