如何優雅的使用LiveData實現一套EventBus(事件總線)

本文已經對《第一行代碼》作者郭霖的公眾號授權獨家發布

前言

EventBus大家都很熟悉了,各種實現方式也是層出不窮,然而,作為有追求的程序員們,永遠在不停的造輪子,畢竟,在程序員的眼中,至今,沒有哪個輪子看上去是完美無暇的。
因此,作為有追求的程序員中的一員,我也想假裝很權威的站出來,然后無所畏懼的從遠古時期講講事件總線的來龍去脈。
有興趣的小伙伴可以搬個椅子聽我白話一番。也許可以給你帶來不一樣的視野。

一、什么是EventBus(事件總線)?


1) 要搞清楚什么是事件總線,我們先了解一下什么叫總線:

總線(Bus)是計算機各種功能部件之間傳送信息的公共通信干線,它是由導線組成的傳輸線束, 按照計算機所傳輸的信息種類,計算機的總線可以劃分為數據總線地址總線控制總線,分別用來傳輸數據、數據地址和控制信號。總線是一種內部結構,它是cpu、內存、輸入、輸出設備傳遞信息的公用通道,主機的各個部件通過總線相連接,外部設備通過相應的接口電路再與總線相連接,從而形成了計算機硬件系統。在計算機系統中,各個部件之間傳送信息的公共通路叫總線,微型計算機是以總線結構來連接各個功能部件的。

  • 其實總線一詞來源于計算機組成原理,計算機總線是一組能為多個部件分時共享的信息傳送線,用來連接多個部件并為之提供信息交換通路。總線不僅是一組信號線,從廣義上講,總線是一組傳送線路及相關的總線協議。

  • 總兒言之,在計算機中用于連接各種功能部件并在它們之間傳送數據的公用線路或通路,我們稱之為總線。

  • 用現實生活打比方,如果把人當作需要傳輸的數據,總線就和日常生活中的公交車一樣,公交車翻譯成Bus,看來很符合實際啊。

2) 在總線上傳輸的數據分很多種,根據用途可以分為以下幾種:

  • 數據總線(Data Bus):在CPURAM之間來回傳送需要處理或是需要儲存的數據。
  • 地址總線(Address Bus):用來指定在RAMRandom Access Memory)之中儲存的數據的地址。
  • 控制總線(Control Bus):將微處理器控制單元(Control Unit)的信號,傳送到周邊設備。
  • 擴展總線(Expansion Bus):外部設備和計算機主機進行數據通信的總線,例如ISA總線,PCI總線。
  • 局部總線(Local Bus):取代更高速數據傳輸的擴展總線。
其實上面的總線都是實際物理存在的概念,而事件本身也是信息的一種形式,為了理解方便,在軟件開發領域,大家便把,在多個模塊間,為事件提供傳輸通道的組件或者說庫,稱之為事件總線,也可以稱之為消息總線

二、為什么要使用事件總線?


1) 搞清楚事件總線的定義之后,我們來分析一下,為什么我們需要使用事件總線:

  • 在軟件開發中,為了軟件前期開發的高效,中期合作的順暢,后期維護的便利,架構師們會施展渾身解數,比如運用面向對象思想,面向對象六大原則(單一、里氏替換原則、依賴倒置原則、接口隔離原則、迪米特法則、開閉原則),以及傳說中的23種設計模式,等花里胡哨的技術,盡量把軟件架構設計得看上去很完美,于是產生了處理各種邏輯的模塊,比如我們熟知的界面層、數據邏輯層等,各層內部也存在處理不同邏輯的組件,面對這些完美的架構設計,這么多的模塊和組件為了相互之間的交互,必定會涉及到數據傳遞。

  • 但是有人會說了,在沒有總線的情況下,我們也是可以進行數據傳遞的,比如參數傳遞,事件回調等。

  • 我們知道,事件總線涉及三個要素:事件源訂閱者通道
    1、當一個模塊處理邏輯時會產生事件,我們稱這個模塊為事件源。
    2、有的模塊等待事件的產生,然后去執行特定的任務,我們稱這個模塊為訂閱者。
    3、維護這些事件源和訂閱者的關系的組件我們稱之為通道,也就是總線。

    想象一下,如果沒有一個通道管理,事件源并不想知道誰會關心自己產生的事件,訂閱者也不想操心是誰產生的事件,因為同樣一個事件可能有N個模塊會產生,也可能會有M個模塊會訂閱,但每個模塊都只希望聚焦于自己的邏輯,如果要通過回調或者其他方式去管理,對誰來說都是一個災難。因此,引入一個和平使者第三方:總線,是面向對象原則的使用,也是很好的一種設計,可以做到更好的解藕。

    image

可以看到,如上圖所示,無論是作為發布事件的組件,還是訂閱事件的組件,在總線的幫助下,都可以很好的進行解藕。

插播說明:我們發現發布-訂閱模式包含生產-消費者模式,也包含觀察-被觀察者模式,其中還有中介者模式,其實,我們常說的23種設計模式,大部分時候并不是單獨存在的,那只是最小設計模式單元,為了便于理解,以上圖為參考畫出如下兩種模式,對比一下你就明白了,這些模式之間的微妙關系。

image

三、EventBus常見實現方式


結合上一章節對事件總線的了解,我們知道,事件總線必不可缺的元素就是事件,另一個必備點就是,當有新的事件時可以通知到關心的模塊,不然其他模塊總要隔三差五的來詢問有沒有事件,那樣效率也太低了吧。

所以事件總線的訂閱和發布是逃不了了,觀察者模式也是板上釘釘的了,為了后面更順暢,在說明常見事件總線實現方式之前,我們還得補充一個小知識:(覺得長的這塊也可以略過)

1) 要實現事件總線,就涉及到事件(信息)的流轉分發,因此,在此之前我們還應該聊一聊信息流轉方式,而信息流轉又分為進程內流轉進程間流轉

  • 進程內流轉又分為單線程和多線程。

    • 單線程數據流轉其實就是參數傳遞,值傳遞或者引用傳遞。
      如函數調用,以及注冊回調函數。
    • 多線程之間數據流轉,其實就是多個線程在操作同一個資源,但是操作的的動作不同。
      如,一個線程對資源進行寫的操作,一個線程對資源進行讀的操作。因為數據在同一個進程中都可以訪問(不考慮私域變量),這個時候考慮的不是數據的流轉,而是同步和互斥問題。
  • 進程間流轉也就是我們常說的進程間通信(IPC,InterProcess Communication)方式。而IPC方式一般有如下方式:說明:可以用于進程間通信的方式,同一個進程內自然也是可以,只是殺雞焉用牛刀。

    • 管道( pipe ):
      管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。
    • 有名管道 (namedpipe) :
      有名管道也是半雙工的通信方式,但是它允許無親緣關系進程間的通信。
    • 信號量(semophore) :
      信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內不同線程之間的同步手段。
    • 消息隊列(messagequeue) :
      消息隊列是由消息的鏈表,存放在內核中并由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩沖區大小受限等缺點。
    • 信號 (signal) :
      信號是一種比較復雜的通信方式,用于通知接收進程某個事件已經發生。
    • 共享內存(shared memory ) :
      共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。
    • 套接字(socket) :
      套接口也是一種進程間通信機制,與其他通信機制不同的是,它可用于不同設備及其間的進程通信。
    • Binder:
      Android優化的跨進程通信方式,更高效的數據傳輸方式。AIDL、BroadCast、AMS等本質都用到了Binder。
  • 了解了信息的流轉方式后,我們再來看看常見的事件總線是怎么實現的吧!

    • 1、直接使用注冊回調方式實現最簡單的觀察者模式,由被觀察者統一管理注冊的觀察者,事件發生時通知所有的觀察者。
      優點:簡單直接,適合部分場景。
      缺點:無法跨線程,更無法跨進程,容易造成內存泄露,要注意注冊(register)和解注冊(unregister)配對使用,沒有生命周期管理等。
    • 2、使用系統提供的廣播機制實現消息事件的訂閱和發布。
      優點:支持跨進程,適合多進程事件總線
      缺點:如果使用非應用內廣播(Local Broadcast),可能造成安全性問題,而且效率不高,建議少用或者不用,且要注意注冊(register)和解注冊(unregister)配對使用,沒有生命周期管理等。
    • 3、GreenRobot提供的EventBus庫利用Handler機制,系統的Message等技術,實現的跨線程通信,是現在大家用得最多的一個事件總線,也是大家公認的EventBus
      優點:可繼承、優先級、粘滯,使用簡單
      缺點:事件分發是通過注解函數的參數類型決定的,這就導致了當接受者過多或相同參數時很難理清消息流。
    • 4、RxBus是基于RxJava實現的一個消息總線,其實網上有很多實現方式,因為核心還是依賴與RxJava,所以實現都大同小異,有興趣可以自己去查查。
      優點:如果不考慮RxJava的代碼,RxBus代碼簡單,是搭配RxJava的天然利器。
      缺點:性能依賴于RxJava,方法調用棧相對來說比較深。
    • 5、LiveEventBus其實也算是依賴于其他庫或者組件發展起來的一種消息總線,目前實現方式也比較雜,各有千秋。
      優點:天然的生命周期感知能力,粘性事件,跨線程能力。原生LiveData的良好擴展。
      缺點:為了解決粘性問題,有各種反射或者侵入性比較強的解決方式。

綜上所見,可能還有其他方式實現事件總線,可以看到各有優缺點,其實也沒有說誰是最好的實現,存在即合理,每種方式都有自己的生存空間,合適即合理,沒有最好的,只有最合適的。

四、LiveData簡介


上面巴拉巴拉了那么多,其實就是想從一個大局方向分析一下事件總線的來龍去脈,也為下面用LiveData實現事件總線做一個伏筆。
雖然市面上有很多事件總線的實現方式,但是基于簡約原則,少即是多,越少變數越安全的理念,能不引入新的第三方時,我盡量使用原生實現。這個帶來的好處有幾個:

  • 1、減小應用的體積。
  • 2、減少引入的不穩定因素,第三方庫隨時有可能停止更新,隨時可能被原生排斥。
  • 3、殺雞不用牛刀,用最合適的方式解決問題。
  • 4、管理方便,統一總體架構。
  • 5、深入思考,學會在現有環境解決現有問題。

那么,什么是LiveData呢?雖然很多人都很熟悉了,但是這里還是本著盡量不讓一個人落下的原則,還是介紹一下:

LiveData是一種可觀察的數據存儲器類。與常規的可觀察類不同,LiveData具有生命周期感知能力,意指它遵循其他應用組件(如 ActivityFragmentService)的生命周期。這種感知能力可確保 LiveData 僅更新處于活躍生命周期狀態的應用組件觀察者。

生命周期感知能力,是不是聽上去很高級,我是覺得很高級,像擁有超能力一樣。
那么這種超能能力又有什么優點呢?

  • 確保界面符合數據狀態

LiveData 遵循觀察者模式。當生命周期狀態發生變化時,LiveData 會通知 Observer 對象。您可以整合代碼以在這些 Observer 對象中更新界面。觀察者可以在每次發生更改時更新界面,而不是在每次應用數據發生更改時更新界面。

  • 不會發生內存泄露

觀察者會綁定到 Lifecycle對象,并在其關聯的生命周期遭到銷毀后進行自我清理。

  • 不會因 Activity 停止而導致崩潰

如果觀察者的生命周期處于非活躍狀態(如返回棧中的 Activity),則它不會接收任何 LiveData 事件。

  • 不再需要手動處理生命周期

界面組件只是觀察相關數據,不會停止或恢復觀察。LiveData 將自動管理所有這些操作,因為它在觀察時可以感知相關的生命周期狀態變化。

  • 數據始終保持最新狀態

如果生命周期變為非活躍狀態,它會在再次變為活躍狀態時接收最新的數據。例如,曾經在后臺的 Activity 會在返回前臺后立即接收最新的數據。

  • 適當的配置更改

如果由于配置更改(如設備旋轉)而重新創建了 ActivityFragment,它會立即接收最新的可用數據。

  • 共享資源

您可以使用單一實例模式擴展 LiveData對象以封裝系統服務,以便在應用中共享它們。LiveData 對象連接到系統服務一次,然后需要相應資源的任何觀察者只需觀察 LiveData 對象。

看了上面這些描述,是不是和我一樣覺得LiveData很高級?下面我們就開始把這高級的東西用起來吧!

五、用LiveData實現簡單的LiveEventBus


話不多說,直接上代碼:

import androidx.lifecycle.MutableLiveData;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by cody.yi. on 2020/6/9.
 * LiveEventBus
 */
public class LiveEventBus {

    public static <T> MutableLiveData<T> getDefault(String key, Class<T> clz) {
        return ready().with(key, clz);
    }

    private final Map<String, MutableLiveData<Object>> bus;

    private LiveEventBus() {
        bus = new HashMap<>();
    }

    private static class InstanceHolder {
        static final LiveEventBus INSTANCE = new LiveEventBus();
    }

    private static LiveEventBus ready() {
        return LiveEventBus.InstanceHolder.INSTANCE;
    }

    @SuppressWarnings("unchecked")
    private <T> MutableLiveData<T> with(String key, Class<T> clz) {
        if (!bus.containsKey(key)) {
            MutableLiveData<Object> liveData = new MutableLiveData<>();
            bus.put(key, liveData);
        }
        return (MutableLiveData<T>) bus.get(key);
    }
}

使用步驟

  • 1、發送數據
LiveEventBus.getDefault("Event1",String.class).setValue("推送數據1");
 或者在非主線程時使用
LiveEventBus.getDefault("Event1",String.class).postValue("推送數據1");
  • 2、監聽數據
LiveEventBus.getDefault("Event1",String.class).observe(this, new Observer<String>() {
            @Override
            public void onChanged(final String event) {
                Toast.makeText(BusDemoActivity.this, "監聽到數據 -> " + event, Toast.LENGTH_SHORT).show();
            }
        });

簡單兩步實現了事件總線,而且使用簡單,不需要手動注冊反注冊操作。LiveData具有的優勢它都有,是不是很香?

六、簡單LiveEventBus包含的問題


其實簡單需求,上面已經夠用了,但是看了LiveData的源碼就會發現,其實還有幾個小瑕疵,在某些特殊情況下會有點問題,下面先逐一分析問題,然后統一給出最終的解決方案。

  • 問題一、因為LiveData機制,默認所有消息都是粘性事件
    也就是說,無論什么時候開始監聽數據變化,之前發送的數據也會被觀察者收到,這在有些場景其實是OK的,但是有些時候就有點不合邏輯了。比如我們做一個價格監聽的事件,用戶預定了一個商品,希望降價的時候提示用戶,如果是粘性事件,就有可能之前的價格變化也會提示用戶,這種時候粘性事件就是多余的。
    另一種場景就很適合粘性事件,比如我們記錄登錄狀態,或者用戶信息,無論我何時進入新頁面,都可以把最后一次用戶信息變化回調給我,這樣還可以一定程度上減少全局變量。

    • 解決方案網上也有很多:
      比如美團大佬用的反射方式,還有包侵入方式修改或者獲取LiveData的包可見字段方式,還有使用字段記錄是否粘性方式(以上各個方案的具體實現可以去參考各位大佬的文章,如有不同看法歡迎討論):
      傳送門1
      傳送門2
      第三種目前看來不太靠譜就不放傳送地址了。

    其實當初看了美團大佬的反射方式,雖然學到了很多,但是,對于反射,我不是很滿意,但是也給大佬提了建議-傳送門
    ,好像大佬也沒有時間改,本著程序員愛折騰的本性,我自己實現了一套更優雅的方式,當然,也是仁者見仁,智者見智了,至少我看來是優雅的。實現完后自己項目中用了很長時間,后來機緣巧合看了別人的分享,也做了一下對比,還是覺得自己的比較優雅,考慮原因有以下幾點。
    1、反射是官方不提倡的,因此也隨時有可能無效,而且版本兼容也是問題,侵入性強。
    2、包可見侵入也是不符合面向對象原則的,人家設置包可見就是不想別人去動里面的東西。不是我的我不動,暫且算是程序員的潔癖吧。
    3、第三種方式是最近看到的,看了一下邏輯,明顯強依賴用戶的使用方式,很容易失效。
    4、侵入性強的方式對混淆有特殊要求,很容易因為混淆配置問題出錯。

  • 問題二、post方式有可能丟失事件
    其實看一下LiveData的源碼就可以發現,postValue方法做了一個小優化,當數據變更太快時,會選擇行的丟掉之前還沒有來得急發送的數據,其實如果只是用于數據監聽,這個優化其實是很好的設計,但是用在事件總線的時候就是一個小坑了。

private final Runnable mPostValueRunnable = new Runnable() {
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                // 緩存里的值已經被發送,被發送了就回到未設置狀態
                mPendingData = NOT_SET;
            }
            setValue((T) newValue);
        }
    };

protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            // 緩存里面是否還有值已被發送
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {//就是這塊返回,導致并不是每個數據都會被執行
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

其實這個問題,我們可以自己把postValue重寫一下就可以了。

  • 問題三、關心事件發送是否在主線程
    LiveData本身提供了兩個方法來更新數據,setValue和postValue,在主線程的時候使用setValue,其他線程使用postValue,作為消息總線,其實可以統一封裝成post方法,在方法里判斷是在什么線程。

  • 問題四、事件名稱隨意寫,容易出錯
    GreenRobot的EventBus是使用類類型作為事件的區分,而我們這種使用字符串來指定事件名稱其實很容易因為寫錯一個大小寫,或者一個字母而導致收不到事件,因此,可以使用統一管理的方式或者去掉手工書寫的方式,比如APT技術自動生成事件進行管理。

  • 問題五、事件無法做到分組,統一打開或關閉事件總線
    比如有老業務突然要下線,以前發送的事件都要取消,這個時候按照業務分組的消息就可以很靈活了,可以直接把相關業務的事件分組關閉,這樣其他地方都不需要修改,什么時候需要打開了也可以打開進行操作,另一種情況,不同業務可能會有相同的消息,比如操作成功消息,大家都叫操作成功,如果按照業務來區分,也是一個不錯的選擇。
    在我的設計里,我把不同業務場景或者分組叫(Group)。

  • 問題六、無法實現跨進程
    因為LiveData本身是單進程數據,不支持跨進程,考慮到跨進程場景比較少,我之前并沒有做支持,考慮到更好的擴展和全面性,新的版本已經做了支持,使用了AIDL和messenger分別實現了一套。為了大家使用方便,項目也做了抽取,可以使用基礎版,也可以使用擴展版。
    傳送門:ElegantBus

  • 問題七、跨進程粘性事件不能和單進程表現一樣
    大家都知道,數據是進程內持有的,跨進程是不共享的,因此使用LiveData的數據跨進程時肯定是沒有的,因此,需要特殊處理。

設計思路以及部分代碼

1、通過包裝類自己管理注冊序號(源碼通過版本管理),解決粘性問題。
2、實現自己的post方式,統一判斷當前所在線程,調用合適的方式。
3、通過定義事件Event和范圍EventGroup,給事件定義含義以及數據類型,然后使用APT技術自動生成需要使用的事件類。
4、提供統一調用方式,也提供簡單測試方式,支持擴展。
5、進程間消息總線擴展,使用AIDL和Messenger兩種實現方式。
6、自己管理進程間的粘性事件,新進程開啟時發送粘性事件到新的進程。

事件值包裹類:

final class ValueWrapper<T> {
    // 每個被觀察的事件數據都有一個序號,只有產生的事件數據在觀察者加入之后才通知到觀察者
    // 即事件數據序號要大于觀察者序號
    final int sequence;
    @NonNull
    final
    T value;

    ValueWrapper(@NonNull T value, int sequence) {
        this.sequence = sequence;
        this.value = value;
    }
}

觀察者包裹類:

public abstract class ObserverWrapper<T> {
    // 每個觀察者都記錄自己序號,只有在進入觀察狀態之后產生的數據才通知到觀察者
    int sequence;
    Observer<ValueWrapper<T>> observer;
}

LiveData包裹類:

/**
 * Created by xu.yi. on 2019/3/31.
 * 和lifecycle綁定的事件總線
 * 每添加一個observer,LiveEventWrapper 的序列號增加1,并賦值給新加的observer,
 * 每次消息更新使用目前的序列號進行請求,持有更小的序列號才需要獲取變更通知。
 * <p>
 * 解決會收到注冊前發送的消息更新問題
 */
@SuppressWarnings("unused")
public class LiveEventWrapper<T> {
    private int mSequence = 0;
    private final MutableLiveData<ValueWrapper<T>> mMutableLiveData;

    public LiveEventWrapper() {
        mMutableLiveData = new MutableLiveData<>();
    }

   /**
     * 如果在多線程中調用,保留每一個值
     * 無需關心調用線程,只要確保在相同進程中就可以
     *
     * @param value 需要更新的值
     */
    public void post(@NonNull T value) {
        checkThread(() -> setValue(value));
    }
   /**
     * 設置監聽之前發送的消息不可以接收到
     * 重寫 observer 的函數 isSticky ,返回true,可以實現粘性事件
     *
     * @param owner           生命周期擁有者
     * @param observerWrapper 觀察者包裝類
     */
    public void observe(@NonNull LifecycleOwner owner, @NonNull ObserverWrapper<T> observerWrapper) {
        observerWrapper.sequence = observerWrapper.isSticky() ? -1 : mSequence++;
        checkThread(() -> mMutableLiveData.observe(owner, filterObserver(observerWrapper)));
    }

 /**
     * 是否是在主線程
     *
     * @return 是主線程
     */
    private boolean isMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }

    /**
     * 檢查線程并執行不同的操作
     *
     * @param runnable 可運行的一段代碼
     */
    private void checkThread(Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        } else {
            // 主線程中觀察
            BusFactory
                    .ready()
                    .getMainHandler()
                    .post(runnable);
        }
    }
}

從包裝類中過濾出原始觀察者:

    /**
     * 從包裝類中過濾出原始觀察者
     *
     * @param observerWrapper 包裝類
     * @return 原始觀察者
     */
    @NonNull
    private Observer<ValueWrapper<T>> filterObserver(@NonNull final ObserverWrapper<T> observerWrapper) {
        if (observerWrapper.observer != null) {
            return observerWrapper.observer;
        }
        return observerWrapper.observer = valueWrapper -> {
            // 產生的事件序號要大于觀察者序號才被通知事件變化
            if (valueWrapper != null && valueWrapper.sequence > observerWrapper.sequence) {
                if (observerWrapper.uiThread()) {
                    observerWrapper.onChanged(valueWrapper.value);
                } else {
                    BusFactory
                            .ready()
                            .getExecutorService()
                            .execute(() -> observerWrapper.onChanged(valueWrapper.value));
                }
            }
        };
    }

通過APT動態生成代碼的部分,因為比較簡單,對APT感興趣的可以去看一下相關文章,也可以參考一下我的源碼。

高端使用方式
1、 定義事件

//定義了事件范圍為demo,激活
@EventGroup(value = "TestScope", active = true)
public class EventDefine {
    @Event(description = "eventInt 事件測試", multiProcess = false, active = true)
    Integer eventInt;

    @Event(description = "eventString 事件測試", multiProcess = true, active = true)
    String eventString;

    @Event(description = "eventBean 事件測試", multiProcess = true, active = true)
    JavaBean eventBean;
}

說明
其實事件定義只用到兩個注解

1)、@EventGroup 使用在 class 上,定義事件分組名,是否激活

2)、@Event 使用在變量上,定義具體 事件描述,是否激活,是否支持多進程

定義完注解后,通過前面導入的注解處理器 annotationProcessor ,ElegantBus 會自動生成以 EventGroup 定義的分組名的事件總線 例如上面的定義就會生成一個 TestScopeBus

然后我們所有地方就可以直接使用這個事件總線進行事件管理。

2、發送事件和監聽事件:

TestScopeBus.eventInt().post(888);
TestScopeBus.eventString().post("新字符串");
TestScopeBus.eventBean().post(new JavaBean());
TestScopeBus.eventInt().observe(owner, new ObserverWrapper<Integer>() {
    @Override
    public void onChanged(final Integer value) {
        ...
    }
});

以上就是總體設計的基本思路和部分代碼,當然,具體設計的時候還是會遇到很多需要考慮的小細節,在此就不一一列舉了,比如跨進程的AIDL和Messager實現我并沒有詳細講解,有興趣的朋友可以可以從源碼中了解更多。

查看源碼https://github.com/codyer/ElegantBus

七、ElegantBus 如何進行更多擴展


  • 支持跨進程 ( 已完成)
  • 支持優先級(計劃中)
  • 支持指定線程監聽(已支持)
  • 其他

說明

關于為什么沒有使用Kotlin而是使用Java實現,在GitHub中有人問到,我也做了回答,主要是基于以下兩點吧:
1、Java我更熟悉,更能對此庫的質量進行保證。
2、現在Kotlin的普及并不是100%,如果使用Kotlin實現,會對Java項目使用者造成一定困擾。但是用Java實現卻不會對Kotlin項目造成困擾。
----------------------------------------------------------- by Cody.yi

謝謝閱讀

如有錯誤缺漏之處歡迎指正!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373