EventBus的使用之重復(fù)早輪子

概述

  • EventBus是Android和Java的發(fā)布/訂閱事件總線。
  • 簡(jiǎn)化了組件之間的通信;
    • 將事件發(fā)送者和接收者分開;
    • 與Activity,F(xiàn)ragment和后臺(tái)線程之間調(diào)度非常好;
    • 避免了復(fù)雜和容易出錯(cuò)的依賴和生命周期問題;
  • 讓你的代碼變得更簡(jiǎn)單易懂;

常用的事件傳遞

  • Intent 意圖,跳轉(zhuǎn)+傳參;
  • Handler ,通常用來在主線程更新UI,使用不當(dāng)容易出現(xiàn)內(nèi)存泄漏;
  • Interface接口,僅限于統(tǒng)一在同一線程中數(shù)據(jù)交互;
  • BoardCastReceiver ,有序+ 無序廣播,不安全或者容易ANR;
  • AIDL 跨進(jìn)程通訊,代碼閱讀星不好,維護(hù)成本高;
  • 其他方式,本地存儲(chǔ);

如何使用

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

Grdle:
compile 'org.greenrobot:eventbus:3.1.1'

如果你的項(xiàng)目需要混淆記得加入:

 -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、定義一個(gè)消息類,該類不繼承任何基類也不要實(shí)現(xiàn)任何接口。如:

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

3、定義事件回調(diào)方法,threadMode是可選:

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

4、在需要訂閱事件的地方注冊(cè)事件(必須要先注冊(cè),不然無法收到消息):

EventBus.getDefault().register(this);

5、發(fā)送消息

 EventBus .getDefault().post(new  MessageEvent());  

6、處理消息,即接受到MessageEvent消息做出反應(yīng):

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

在3.0之前,EventBus還沒有使用注解方式。消息處理的方法也只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分別代表四種線程模型。而在3.0之后,消息處理的方法可以隨便取名,但是需要添加一個(gè)注解@Subscribe,并且要指定線程模型(默認(rèn)為PostThread),四種線程模型,下面會(huì)講到。
注意,事件處理函數(shù)的訪問權(quán)限必須為public,否則會(huì)報(bào)異常。
7、取消消息事件的訂閱:

 EventBus.getDefault().unregister(this);

EventBus有何優(yōu)點(diǎn)

采用消息發(fā)布/訂閱的一個(gè)很大的優(yōu)點(diǎn)就是代碼的簡(jiǎn)潔性,并且能夠有效地降低消息發(fā)布者和訂閱者之間的耦合度;
舉個(gè)例子,比如有兩個(gè)界面,ActivityA和ActivityB,從ActivityA界面跳轉(zhuǎn)到ActivityB界面后,ActivityB要給ActivityA發(fā)送一個(gè)消息,ActivityA收到消息后在界面上顯示出來,我可以告訴你,這樣也可以,就是代碼過于臃腫;

常用API介紹

線程模型

在EventBus的事件處理函數(shù)中需要指定線程模型,即指定事件處理函數(shù)運(yùn)行所在的想線程。在上面我們已經(jīng)接觸到了EventBus的四種線程模型。那他們有什么區(qū)別呢?
在EventBus中的觀察者通常有四種線程模型,分別是PostThread(默認(rèn))、MainThread、BackgroundThread與Async。

  • PostThread:如果使用事件處理函數(shù)指定了線程模型為PostThread,那么該事件在哪個(gè)線程發(fā)布出來的,事件處理函數(shù)就會(huì)在這個(gè)線程中運(yùn)行,也就是說發(fā)布事件和接收事件在同一個(gè)線程。在線程模型為PostThread的事件處理函數(shù)中盡量避免執(zhí)行耗時(shí)操作,因?yàn)樗鼤?huì)阻塞事件的傳遞,甚至有可能會(huì)引起ANR。

  • MainThread:如果使用事件處理函數(shù)指定了線程模型為MainThread,那么不論事件是在哪個(gè)線程中發(fā)布出來的,該事件處理函數(shù)都會(huì)在UI線程中執(zhí)行。該方法可以用來更新UI,但是不能處理耗時(shí)操作。

  • BackgroundThread:如果使用事件處理函數(shù)指定了線程模型為

  • BackgroundThread,那么如果事件是在UI線程中發(fā)布出來的,那么該事件處理函數(shù)就會(huì)在新的線程中運(yùn)行,如果事件本來就是子線程中發(fā)布出來的,那么該事件處理函數(shù)直接在發(fā)布事件的線程中執(zhí)行。在此事件處理函數(shù)中禁止進(jìn)行UI更新操作。
    Async:如果使用事件處理函數(shù)指定了線程模型為Async,那么無論事件在哪個(gè)線程發(fā)布,該事件處理函數(shù)都會(huì)在新建的子線程中執(zhí)行。同樣,此事件處理函數(shù)中禁止進(jìn)行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());
    }
    

分別使用上面四個(gè)方法訂閱同一事件,打印他們運(yùn)行所在的線程。首先我們?cè)赨I線程中發(fā)布一條MessageEvent的消息,看下日志打印結(jié)果是什么。
打印結(jié)果如下:

postEvent﹕ main
PostThread﹕ main
Async﹕ pool-1-thread-1
MainThread﹕ main
BackgroundThread﹕ pool-1-thread-2

從日志打印結(jié)果可以看出,如果在UI線程中發(fā)布事件,則線程模型為PostThread的事件處理函數(shù)也執(zhí)行在UI線程,與發(fā)布事件的線程一致。線程模型為Async的事件處理函數(shù)執(zhí)行在名字叫做pool-1-thread-1的新的線程中。而MainThread的事件處理函數(shù)執(zhí)行在UI線程,BackgroundThread的時(shí)間處理函數(shù)執(zhí)行在名字叫做pool-1-thread-2的新的線程中。

再看看在子線程中發(fā)布一條MessageEvent的消息時(shí),會(huì)有什么樣的結(jié)果。

打印結(jié)果如下:

postEvent﹕ Thread-1
PostThread﹕ Thread-1
BackgroundThread﹕ Thread-1
Async﹕ pool-1-thread-1
MainThread﹕ main

從日志打印結(jié)果可以看出,如果在子線程中發(fā)布事件,則線程模型為PostThread的事件處理函數(shù)也執(zhí)行在子線程,與發(fā)布事件的線程一致(都是Thread-1)。BackgroundThread事件模型也與發(fā)布事件在同一線程執(zhí)行。Async則在一個(gè)名叫pool-1-thread-1的新線程中執(zhí)行。MainThread還是在UI線程中執(zhí)行。

黏性事件

除了上面講的普通事件外,EventBus還支持發(fā)送黏性事件。臥槽,黏性事件?簡(jiǎn)單講,就是在發(fā)送事件之后再訂閱該事件也能收到該事件,一些事件在事件發(fā)布后攜帶有興趣的信息。例如,一個(gè)事件表示一些初始化完成?;蛘呷绻幸恍﹤鞲衅骰蛭恢脭?shù)據(jù),并且想要保留最新的值。而不是實(shí)現(xiàn)自己的緩存,你可以使用粘性事件。所以EventBus將最后一個(gè)特定類型的粘滯事件保存在內(nèi)存中。然后粘性事件可以傳遞給訂閱者或者顯式查詢。因此,您不需要任何特殊的邏輯來考慮已有的數(shù)據(jù)。跟黏性廣播類似。具體用法如下:
訂閱黏性事件:

EventBus.getDefault().register(StickyModeActivity.this);

黏性事件處理函數(shù):

@Subscribe(sticky = true)
public void XXX(MessageEvent messageEvent) {
......
}

發(fā)送黏性事件:

EventBus.getDefault().postSticky(new MessageEvent("test"));

手動(dòng)獲取和刪除粘性事件

  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方法被重載:當(dāng)你傳入類時(shí),它將返回以前持有的粘性事件。使用這個(gè)變體,我們可以改進(jìn)前面的例子:

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的源碼應(yīng)該知道,EventBus的實(shí)現(xiàn)使用了觀察者模式。代碼如下:

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();
        //排除系統(tǒng)的類或接口,不能是系統(tǒng)的類或接口,因?yàn)槲覀兊挠嗛喎椒ㄖ荒苁俏覀冏约旱念惢蚪涌?        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("只能有一個(gè)參數(shù)");

            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("必須有一個(gè)訂閱方法");
    }

    return subscriberMethods;
}

看到當(dāng)EventBus開始register的時(shí)候,通過解析Class中被標(biāo)注@Sunscriber注解的方法Method,拿到注解標(biāo)識(shí)的接收事件的方法,然后解析注解并將解析到的Method和Method Param type以及ThreadMode保存到內(nèi)存中,在來看看那post方法,代碼如下:

 /**
 * 實(shí)際上是根據(jù)訂閱方法的參數(shù)和發(fā)布傳進(jìn)來的對(duì)象進(jìn)行對(duì)比
 *
 * @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);
}

當(dāng)post的時(shí)候,通過post傳入的參數(shù)類型與第2步解析的得到的方法參數(shù)類型進(jìn)行對(duì)比,回調(diào)的對(duì)應(yīng)的方法,整個(gè)過程就結(jié)束了。

總結(jié):

1、因?yàn)镋ventBus 3.0之前,在運(yùn)行時(shí)大量存在了反射,勢(shì)必會(huì)造成不必要的性能問題,目前官方做了優(yōu)化,使用了編譯時(shí)注解,也就是index,如何使用Eventbus注解。

2、EventBus并不支持跨進(jìn)程通訊,我們是否可以對(duì)他進(jìn)程拓展?那么這樣會(huì)不會(huì)和第1的問題沖突呢?

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言:EventBus出來已經(jīng)有一段時(shí)間了,github上面也有很多開源項(xiàng)目中使用了EventBus。所以抽空學(xué)習(xí)...
    Kerry202閱讀 1,373評(píng)論 1 2
  • 目錄 1.概述 2.實(shí)戰(zhàn) 1.基本框架搭建 2.新建一個(gè)類FirstEvent 3.在要接收消息的頁面注冊(cè)Even...
    慕涵盛華閱讀 10,631評(píng)論 2 16
  • EventBus這個(gè)開源框架出來已經(jīng)很久了,深的很多開發(fā)者青睞,由greenrobot組織貢獻(xiàn)(該組織還貢獻(xiàn)了gr...
    Scus閱讀 2,310評(píng)論 0 0
  • 前言:EventBus出來已經(jīng)有一段時(shí)間了,github上面也有很多開源項(xiàng)目中使用了EventBus。所以抽空學(xué)習(xí)...
    Lauren_Liuling閱讀 48,668評(píng)論 23 155
  • 前言 最近在公司做一個(gè)類似于手機(jī)工廠模式的一個(gè)項(xiàng)目,用來檢測(cè)其他各個(gè)App是否正常工作,所以要求是盡可能的輕量級(jí),...
    Luckily_Liu閱讀 1,257評(píng)論 2 8

友情鏈接更多精彩內(nèi)容