EventBus

EventBus是一個(gè)基于觀察者模式的事件發(fā)布/訂閱框架圃泡,開(kāi)發(fā)者通過(guò)極少的代碼去實(shí)現(xiàn)多個(gè)模塊之間的通信垮抗,而不需要以層層傳遞接口的形式去單獨(dú)構(gòu)建通信橋梁虏束。從而降低因多重回調(diào)導(dǎo)致的模塊間強(qiáng)耦合洪添,同時(shí)避免產(chǎn)生大量?jī)?nèi)部類绅项。擁有使用方便曲尸,性能高赋续,接入成本低,支持多線程的優(yōu)點(diǎn)另患。

Paste_Image.png
一蚕捉、如何實(shí)現(xiàn)Eventbus
1 定義事件

事件是POJO(plain old java object)類型,不需要什么特別的需求

public class MessageEvent { 
public final String message; 
public MessageEvent(String message) { 
  this.message = message; 
}
2 準(zhǔn)備訂閱者

訂閱者實(shí)現(xiàn)事件處理方法(也叫做訂閱者方法),這個(gè)方法會(huì)在事件提交的時(shí)候被調(diào)用柴淘。這些是使用@Subscribe注解定義的迫淹。請(qǐng)注意EventBus 3的方法名字可以自由選擇(不像EventBus 2中約束的那樣)。

// 當(dāng)一個(gè)Message Event提交的時(shí)候這個(gè)方法會(huì)被調(diào)用
@Subscribe
public void onMessageEvent(MessageEvent event){
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

// 當(dāng)一個(gè)SomeOtherEvent被提交的時(shí)候這個(gè)方法被調(diào)用为严。
@Subscribe
public void handleSomethingElse(SomeOtherEvent event){
    doSomethingWith(event);
}

訂閱者也需要在bus中注冊(cè)和注銷敛熬。只有在訂閱者注冊(cè)的時(shí)候,他們才會(huì)收到事件第股。在Android中应民,Activities和Fragments通常綁定他們的生命周期。

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
   EventBus.getDefault().unregister(this);
    super.onStop();
}
3 提交事件

在代碼中任意位置提交事件夕吻。所有當(dāng)前注冊(cè)的匹配事件類型的訂閱者都會(huì)收到事件诲锹。

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
4 線程間傳遞(線程模式)

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

PostThread:

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

MainThread:

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

BackgroundThread:

如果使用事件處理函數(shù)指定了線程模型為BackgroundThread阎曹,那么如果事件是在UI線程中發(fā)布出來(lái)的伪阶,那么該事件處理函數(shù)就會(huì)在新的線程中運(yùn)行,如果事件本來(lái)就是子線程中發(fā)布出來(lái)的处嫌,那么該事件處理函數(shù)直接在發(fā)布事件的線程中執(zhí)行栅贴。在此事件處理函數(shù)中禁止進(jìn)行UI更新操作。

Async:

如果使用事件處理函數(shù)指定了線程模型為Async熏迹,那么無(wú)論事件在哪個(gè)線程發(fā)布檐薯,該事件處理函數(shù)都會(huì)在新建的子線程中執(zhí)行。同樣注暗,此事件處理函數(shù)中禁止進(jìn)行UI更新操作坛缕。

@Subscribe(threadMode = ThreadMode.PostThread)
public void onMessageEventPostThread(MessageEvent messageEvent) {
    Log.e("PostThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.MainThread)
public void onMessageEventMainThread(MessageEvent messageEvent) {
    Log.e("MainThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.BackgroundThread)
public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
    Log.e("BackgroundThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.Async)
public void onMessageEventAsync(MessageEvent messageEvent) {
    Log.e("Async", Thread.currentThread().getName());
}

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

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e("postEvent", Thread.currentThread().getName());
            EventBus.getDefault().post(new MessageEvent());
        }
    });

打印結(jié)果如下:

log --> E/postEvent﹕ main
log --> E/PostThread﹕ main
log --> E/Async﹕ pool-1-thread-1
log --> E/MainThread﹕ main
log --> E/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的新的線程中遍烦。

我們?cè)倏纯丛谧泳€程中發(fā)布一條MessageEvent的消息時(shí)俭嘁,會(huì)有什么樣的結(jié)果。

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.e("postEvent", Thread.currentThread().getName());
                    EventBus.getDefault().post(new MessageEvent());
                }
            }).start();
        }
    });

打印結(jié)果如下:

log --> E/postEvent﹕ Thread-125
log --> E/PostThread﹕ Thread-125
log --> E/BackgroundThread﹕ Thread-125
log --> E/Async﹕ pool-1-thread-1
log --> E/MainThread﹕ main

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

上面一個(gè)例子充分驗(yàn)證了指定不同線程模型的事件處理方法執(zhí)行所在的線程泄私。

5 黏性事件

除了上面講的普通事件外,EventBus還支持發(fā)送黏性事件备闲。何為黏性事件呢晌端?簡(jiǎn)單講,就是在發(fā)送事件之后再訂閱該事件也能收到該事件恬砂,跟黏性廣播類似咧纠。具體用法如下:

訂閱黏性事件:

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

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

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

發(fā)送黏性事件:

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

處理消息事件以及取消訂閱和上面方式相同。
看個(gè)簡(jiǎn)單的黏性事件的例子泻骤,為了簡(jiǎn)單起見(jiàn)我這里就在一個(gè)Activity里演示了漆羔。
Activity代碼:

public class StickyModeActivity extends AppCompatActivity {

    int index = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sticky_mode);
        findViewById(R.id.post).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().postSticky(new MessageEvent("test" + index++));
            }
        });
        findViewById(R.id.regist).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().registerSticky(StickyModeActivity.this);
            }
        });

        findViewById(R.id.unregist).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().unregister(StickyModeActivity.this);
            }
        });

    }

    @Subscribe(threadMode = ThreadMode.PostThread, sticky = true)
    public void onMessageEventPostThread(MessageEvent messageEvent) {
        Log.e("PostThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.MainThread, sticky = true)
    public void onMessageEventMainThread(MessageEvent messageEvent) {
        Log.e("MainThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.BackgroundThread, sticky = true)
    public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
        Log.e("BackgroundThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.Async, sticky = true)
    public void onMessageEventAsync(MessageEvent messageEvent) {
        Log.e("Async", messageEvent.getMessage());
    }

}

布局代碼activity_sticky_mode.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.lling.eventbusdemo.StickyModeActivity">

    <Button
        android:id="@+id/post"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Post"/>

    <Button
        android:id="@+id/regist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Regist"/>

    <Button
        android:id="@+id/unregist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UnRegist"/>

</LinearLayout>

代碼很簡(jiǎn)單擦剑,界面上三個(gè)按鈕璧帝,一個(gè)用來(lái)發(fā)送黏性事件温技,一個(gè)用來(lái)訂閱事件永部,還有一個(gè)用來(lái)取消訂閱的赁遗。首先在未訂閱的情況下點(diǎn)擊發(fā)送按鈕發(fā)送一個(gè)黏性事件邑跪,然后點(diǎn)擊訂閱榜轿,會(huì)看到日志打印結(jié)果如下:

log --> E/PostThread﹕ test0
log --> E/Async﹕ test0
log --> E/MainThread﹕ test0
log --> E/BackgroundThread﹕ test0

這就是粘性事件亥贸,能夠收到訂閱之前發(fā)送的消息器虾。但是它只能收到最新的一次消息讯嫂,比如說(shuō)在未訂閱之前已經(jīng)發(fā)送了多條黏性消息了,然后再訂閱只能收到最近的一條消息兆沙。這個(gè)我們可以驗(yàn)證一下端姚,我們連續(xù)點(diǎn)擊5次POST按鈕發(fā)送5條黏性事件,然后再點(diǎn)擊REGIST按鈕訂閱挤悉,打印結(jié)果如下:

log --> E/PostThread﹕ test4
log --> E/MainThread﹕ test4
log --> E/Async﹕ test4
log --> E/BackgroundThread﹕ test4

由打印結(jié)果可以看出渐裸,確實(shí)是只收到最近的一條黏性事件。

6 配置

EventBusBuilder用來(lái)配置EventBus装悲。比如昏鹃,如果一個(gè)提交的事件沒(méi)有訂閱者,可以使EventBus保持安靜诀诊。

EventBus eventBus = EventBus.builder().logNoSubscriberMessages(false)
    .sendNoSubscriberEvent(false).build()

另一個(gè)例子是當(dāng)一個(gè)訂閱者拋出一個(gè)異常的失敗洞渤。注意:默認(rèn)情況下,EventBus捕獲異常從onEvent方法中拋出并且發(fā)出一個(gè)SubscriberExceptionEvent 属瓣,這個(gè)事件可以不必處理载迄。

EventBus eventBus = EventBus.builder().throwSubscriberException(true).build();

配置默認(rèn)EventBus實(shí)例使用EventBus.getDefault()是一種簡(jiǎn)單的方法來(lái)獲取共享的EventBus實(shí)例讯柔。EventBusBuilder也可以使用installDefaultEventBus()方法來(lái)配置這個(gè)默認(rèn)的實(shí)例。比如护昧,當(dāng)在onEvent方法中發(fā)生異常的時(shí)候魂迄,可以配置默認(rèn)的EventBus實(shí)例來(lái)重新拋出異常。建議在使用DEBUG模式的時(shí)候這么使用惋耙,因?yàn)檫@樣app會(huì)因?yàn)檫@個(gè)異常而崩潰捣炬。

EventBus.builder().throwSubscriberException(BuildConfig.DEBUG)
.installDefaultEventBus();

注意:只有在默認(rèn)EventBus實(shí)例在第一次使用之前這么配置一次。后續(xù)調(diào)用installDefaultEventBus() 會(huì)拋出異常绽榛。這確保應(yīng)用程序的行為一致湿酸。可以在Application類中配置默認(rèn)的EventBus灭美。

7 訂閱者索引

對(duì)于上面所描述的EventBus的功能推溃,是通過(guò)Java反射來(lái)獲取訂閱方法,這樣以來(lái)大大降低了EventBus的效率届腐,同時(shí)也影響了我們應(yīng)用程序的效率铁坎。其實(shí)對(duì)于反射的處理解析不僅僅只能夠通過(guò)Java反射的方式來(lái)進(jìn)行,還能夠通過(guò)apt(Annotation Processing Tool)來(lái)處理梯捕。為了提高效率厢呵,EventBus提供這中方式來(lái)完成EventBus的執(zhí)行過(guò)程。下面就來(lái)看一下對(duì)于EventBus的另一種使用方式傀顾。
在Project的build.gradle中添加如下代碼:

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

然后在app的build.gradle中添加如下代碼襟铭。

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

重新rebuild之后會(huì)在build目錄下面生成MyEventBusIndex文件,文件名可以自定義短曾。下面就來(lái)看一下如何使用這個(gè)MyEventBusIndex.我們可以自定義設(shè)置自己的EventBus來(lái)為其添加MyEventBusIndex對(duì)象寒砖。代碼如下所示:

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

我們也能夠?qū)yEventBusIndex對(duì)象安裝在默認(rèn)的EventBus對(duì)象當(dāng)中。代碼如下所示:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();

剩下操作與之前EventBus的一樣嫉拐。當(dāng)然也建議通過(guò)添加訂閱者索引這種方式來(lái)使用EventBus哩都,這樣會(huì)比通過(guò)反射的方式來(lái)解析注解效率更高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婉徘,一起剝皮案震驚了整個(gè)濱河市漠嵌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盖呼,老刑警劉巖儒鹿,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異几晤,居然都是意外死亡约炎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)圾浅,“玉大人掠手,你說(shuō)我怎么就攤上這事±瓴叮” “怎么了喷鸽?”我有些...
    開(kāi)封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)府寒。 經(jīng)常有香客問(wèn)我魁衙,道長(zhǎng)报腔,這世上最難降的妖魔是什么株搔? 我笑而不...
    開(kāi)封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮纯蛾,結(jié)果婚禮上纤房,老公的妹妹穿的比我還像新娘。我一直安慰自己翻诉,他們只是感情好炮姨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著碰煌,像睡著了一般舒岸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芦圾,一...
    開(kāi)封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天蛾派,我揣著相機(jī)與錄音,去河邊找鬼个少。 笑死洪乍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夜焦。 我是一名探鬼主播壳澳,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茫经!你這毒婦竟也來(lái)了巷波?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卸伞,失蹤者是張志新(化名)和其女友劉穎抹镊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體瞪慧,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡髓考,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弃酌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氨菇。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儡炼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出查蓉,到底是詐尸還是另有隱情乌询,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布豌研,位于F島的核電站妹田,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鹃共。R本人自食惡果不足惜鬼佣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望霜浴。 院中可真熱鬧晶衷,春花似錦、人聲如沸阴孟。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)永丝。三九已至锹漱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慕嚷,已是汗流浹背哥牍。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闯冷,地道東北人砂心。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蛇耀,于是被迫代替她去往敵國(guó)和親辩诞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言:EventBus出來(lái)已經(jīng)有一段時(shí)間了纺涤,github上面也有很多開(kāi)源項(xiàng)目中使用了EventBus译暂。所以抽空學(xué)習(xí)...
    Kerry202閱讀 1,286評(píng)論 1 2
  • 前言:EventBus出來(lái)已經(jīng)有一段時(shí)間了,github上面也有很多開(kāi)源項(xiàng)目中使用了EventBus撩炊。所以抽空學(xué)習(xí)...
    Lauren_Liuling閱讀 48,466評(píng)論 23 155
  • 對(duì)于Android開(kāi)發(fā)老司機(jī)來(lái)說(shuō)肯定不會(huì)陌生外永,它是一個(gè)基于觀察者模式的事件發(fā)布/訂閱框架,開(kāi)發(fā)者可以通過(guò)極少的代碼...
    飛揚(yáng)小米閱讀 1,475評(píng)論 0 50
  • 目錄 1.概述 2.實(shí)戰(zhàn) 1.基本框架搭建 2.新建一個(gè)類FirstEvent 3.在要接收消息的頁(yè)面注冊(cè)Even...
    慕涵盛華閱讀 10,502評(píng)論 2 16
  • 請(qǐng)老師指正
    Na滋味閱讀 176評(píng)論 0 0