放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

W_BinaryTree發表於2017-05-02

這一系列文章本來我發表在簡書。最近開始轉移到掘金。以後也會在掘金髮表(慢慢拋棄簡書了應該,掘金的技術環境確實比簡書好些)。

EventBus和Otto在之前作為Android元件間通訊工具,簡單方便十分受歡迎,但是也非常容易Abuse。大概有如下幾個缺點:

  • 由於是Event,在釋出Event的時候就要做好準備可能並沒有人接受這個Event, Subscribe的時候也要做好準備可能永遠不會收到Event。Event無論順序還是時間上都某種程度上不太可控。如果你將資料寄託在Event上然後就直接在Android其他生命週期方法中直接使用這個資料或成員變數。那麼很有可能你會得到NPE。
  • EventBus看似將你的程式解耦,但是又有些過了。我們常常使用EventBus傳資料,這已經是Dependency級別的資料而不是一個可以被解耦出來的模組。這樣就造成了過多EventBus的程式碼會造成程式碼結構混亂,難以測試和追蹤,違背瞭解耦的初衷。這時如果有意或無意的造成了Nested Event。那情況會更糟。

由於EventBus的種種缺點,以及後面RxJava的出現。很多人都開始使用RxJava來取代EventBus。甚至Otto的官方介紹裡都寫到:

Deprecated!

This project is deprecated in favor of RxJava and
RxAndroid. These projects permit the same event-driven
programming model as Otto, but they’re more capable and offer better control of threading.

If you’re looking for guidance on migrating from Otto to Rx, this post
is a good start.

連結是一個教你怎麼使用RxJava來自己手動寫一個RxBus來代替EventBus的文章。雖然看起來是在用RxJava。但是實際上卻仍然在用EventBus。甚至這個封裝其實也並沒有GreenRobot或者Otto來的好。
我們看看Jake Wharton對RxBus的評價:

放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

我想"RxBus"唯一的好處就是他是一個Rx的入門毒品。否則的話,你要麼就不是在用Rx,要麼你需要更加慣用的Rx資源 (渣翻譯見諒)

再來一個GitHub的:

放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

subscribeActual部分我們先不考慮。然而Jake指出最好不要使用Relay來“重新發明”Event Bus.

這裡看圖說話:
Jake Wharton在GOTO 2016 上的講座中提到,我們正常的Android程式設計是這樣的:

放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

我們像一箇中間人一樣。
而使用RxJava。 我們的結構,更像這樣
放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

我們使用RxJava來直接把元件相連,對所接受到的資料作出反應,所謂的 "Reactive"。
而使用Eventbus? Jake 沒說, 我自己畫一個:

放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

我們作為一箇中間人,傳遞訊息。EventBus作為另一箇中間人。幫我們傳遞訊息。(這也就是所謂的“看似解耦”)

再打個比方,雖然我們將EventBus翻譯成時間匯流排,但是其實匯流排就是Bus也就是公交車。而RxJava更像一個專車,Uber或者滴滴。他直接連結你的兩個或多個需要通訊的類。傳輸資料,當然你可以做一個很大的專車,穿梭在所有類之間,也就是所謂的RxBus。所以在這裡為什麼放棄RxBus也就不言而喻了不是?

那麼,問題來了?

怎樣才是正確(正常?)的RxJava使用方式?

其實Jake 也在GitHub的討論上給出了一個答案:

放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

所以應該是,每當你想釋出一個Event在EventBus時,直接暴露一個Observable出來。每當你想接受一個Event時,找到這個Observable並且Subscribe他。

這樣做的好處是什麼?

  • 目標和地點都很明確。你的Subscriber明確的知道他Subscribe的是誰,而且明確的知道我需要作出什麼反應。這也正是RxJava的核心“響應式程式設計”。
  • 由於使用了Observable,對於異常處理將會非常方便。而且還有功能強大全面的Operator來輔助你。
  • 雖然看起來耦合性有所增加。但是這是必要的,上面也說過,EventBus雖然在程式碼上看似解耦。其實他們還是聯絡在一起的。而我們這樣直接暴露Observable給需要的其他類,這完成了1 -> 1/N的連結,而不需要EventBus這個中間人來傳遞訊息/事件,而且保證我們需要的事件一定會直接到達。

我們來舉個例子

放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

上下兩個Fragment,上面的一個EditText,下面的一個TextView。上面的EditText變化的時候下面的TextView也跟著變化。

先把EditText的TextChangedListener封裝在Observable裡:

        stringObservable = Observable.create(e -> editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                e.onNext(s.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        }));

/**
***
*/
    //Expose Observable
    public Observable<String> getEditTextObservable() {
        return stringObservable;
    }複製程式碼

不習慣自己封裝可以使用RxBinding :

        stringObservable = RxTextView.textChangeEvents(editText)
                                     .map(event -> event.text().toString());複製程式碼

再從我們的TextViewFragment中 取到這個封裝好的Observable:

    @Override
    public void onStart() {
        super.onStart();
        FragmentEditText fragment = (FragmentEditText) getFragmentManager().findFragmentByTag(FragmentEditText.TAG);
        if(fragment != null){
            fragment.getStringObservable().subscribe(s -> textView.setText(s));
        }
    }複製程式碼

來看看效果:

放棄RxBus,擁抱RxJava(一):為什麼避免使用EventBus/RxBus | 掘金技術徵文

當然,這裡還有個問題

  • 由於我們將editText封裝在Observable裡,無論是create()方法還是使用RxBinding,都會持有這個View的強引用。造成記憶體洩漏。所以我們一定要在最後加入dispose()方法來釋放。所以我推薦使用RxBinding,他已經幫我們在dispose()方法裡寫好了解除Listener的方法。
  • 因為並沒有使用publish操作符,導致多個Subscriber的時候還是有些許問題。可以考慮直接加入.share().

總結:

前幾天我在Reddit上看到一個人的回覆:

I think EventBus on android is popular because people don't know how to share a java object reference between android components like a Fragment and an Activity, or 2 Activities and so on. So basically I think people don't know how 2 Activites can observe the same object for data changes which I think comes from the fact that we still don't know how to architect our apps properly.

我認為 EventBus在Android上火爆的原因是人們不知道怎麼去在Android元件,例如Activity/Fragment之間共享一個Java物件的引用。

這個回覆可以說應該是觸到了很多人的痛點。很多情況我們用EventBus僅僅是不知道如何在多個Fragment/Activity之間共享一個物件。EventBus的做法是在Bus裡登記所有的接受者。這點在RxJava裡類似,Subject/ConnectableObservable 都有類似的功能。但問題是EventBus作為一個全域性Bus,各種不同型別的事件管理會很麻煩(雖然EventBus把這些事給你做好了,RxBus要自己弄)。我們有了RxJava完全可以避免不同事件的管理。相同事件封裝成對應Observable,根據需求選擇訂閱。這樣保持了型別安全,提高了效能,邏輯更清晰。

想一想,自己使用EventBus是不是也是這個原因呢?

掘金技術正文

相關文章