美團(tuán)是如何使用LiveData搭建通信框架的寇漫?

前言

之前的文章中我們介紹了LiveData的內(nèi)部實現(xiàn)遣耍,并提出LiveData也可用作應(yīng)用程序內(nèi)的通信手段。而目前炮车,網(wǎng)絡(luò)上也確實有很多相關(guān)的實現(xiàn)文章舵变。在眾多實現(xiàn)中酣溃,來自美團(tuán)的實現(xiàn)是質(zhì)量最有保證的。但是美團(tuán)的實現(xiàn)不止是使用LiveData纪隙,還涉及廣播赊豌、跨進(jìn)程通信等;本文將從美團(tuán)的實現(xiàn)中抽取LiveData相關(guān)的內(nèi)容绵咱,重新構(gòu)建一個通信框架(畢竟跨進(jìn)程等通信手段并不是每個項目都會使用到)碘饼。如果有對LiveData內(nèi)部機(jī)制不了解的同學(xué),強(qiáng)烈建議先閱讀上篇文章《LiveData為何這么香悲伶,這些你都知道嗎艾恼?》再來看本文!

推薦

文章將率先在公眾號「Code滿滿」與個人博客「李益的小站」上發(fā)布麸锉,如果本文對你有幫助钠绍,就關(guān)注一下公眾號吧!

一花沉、需要做什么

在開始正式的代碼講解前柳爽,我們需要知道我們要做什么。在LiveData中共有兩種訂閱方式:observe()observeForever()碱屁。其中observeForever()的訂閱非常簡單磷脯,觀察者永遠(yuǎn)接收消息,中間不做其他判斷娩脾,基本沒有什么可以改動的地方赵誓。而observe()會對觀察者所在頁面的狀態(tài)進(jìn)行判斷,如果頁面處于活躍狀態(tài)晦雨,則接收數(shù)據(jù)更新架曹,否則不接收。顯而易見闹瞧,observe()對頁面狀態(tài)進(jìn)判斷的部分绑雄,是我們可以放手施為的地方。

了解了LiveData的兩種訂閱方式后奥邮,我們再來思考一下我們的通信框架應(yīng)該如何搭建:

  • 為了方便使用万牺,這個框架最好是個單例
  • 為了可以靈活控制頁面是否處于活躍狀態(tài)(這會影響觀察者在何時可以接收到消息),需要重寫LiveData中對頁面活躍狀態(tài)的判斷
  • 為了靈活的控制事件洽腺,需要有個池子來存儲事件脚粟,這個池子可以是Map,當(dāng)然有存儲也肯定需要有刪除
  • LiveData原來只是提供了observeobserveForever方法蘸朋,缺少類似EventBus中的粘性事件核无,我們需要補(bǔ)上

以上大致就是我們需要在搭建框架時需要注意的一些點(diǎn),下面我們一點(diǎn)一點(diǎn)來實現(xiàn)藕坯。

二团南、XLiveData

我們先從修改LiveData中對頁面活躍狀態(tài)的判斷開始噪沙。我們新建一個類,命名為XLiveData吐根,此類主要用于重寫LiveData中判斷頁面是否處于活躍狀態(tài)的相關(guān)方法正歼,所以XLiveData需要繼承于MutableLiveData

public class XLiveData<T> extends MutableLiveData<T> {
    ......
}

1. XLifecycleBoundObserver

XLiveData中拷橘,定義一個繼承于LifecycleBoundObserver的內(nèi)部類局义,命名為XLifecycleBoundObserver,用于代替LiveData內(nèi)部的LifecycleBoundObserver冗疮。

protected Lifecycle.State observerActiveLevel() {
    return STARTED;
}

private class XLifecycleBoundObserver extends LifecycleBoundObserver {
    XLifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
        super(owner, observer);
    }
    
    @Override
    boolean shouldBeActive() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(observerActiveLevel());
    }
}

XLifecycleBoundObserverLifecycleBoundObserver相比萄唇,主要是重寫了shouldBeActive()方法。LifecycleBoundObserver中的shouldBeActive()方法是當(dāng)Activity處于onStart()與onPause()生命狀態(tài)之間時赌厅,判定頁面處于活躍狀態(tài)穷绵,返回true。此處改為我們可以自行控制特愿,更加靈活仲墨,例如我們可以改為Activity處于onCreate()與onStop()生命狀態(tài)之間時,判定頁面處于活躍狀態(tài)揍障,返回true目养。

@Override
boolean shouldBeActive() {
    return mOwner.getLifecycle().getCurrentState().isAtLeast(observerActiveLevel());
}

2. 重寫observe

既然重寫了一個XLifecycleBoundObserver,那么LiveData的observe()方法也必須得重寫毒嫡,主要是將其中的LifecycleBoundObserver替換為XLifecycleBoundObserver癌蚁。

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    //  super.observe(owner, observer);
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    try {
        LifecycleBoundObserver wrapper = new XLifecycleBoundObserver(owner, (Observer<T>) observer);
        LifecycleBoundObserver existing = (LifecycleBoundObserver) callMethodPutIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                        + " with different lifecycles");
            }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

三、BusObservable

BusObservable是一個接口兜畸,定義了被觀察者需要實現(xiàn)的方法努释。因為真正編寫被觀察者代碼時,代碼如果過多咬摇,閱讀體驗不會太好伐蒂,影響我們對被觀察者的理解,此處先用接口定義結(jié)構(gòu)肛鹏,方便我們理解逸邦。

public interface BusObservable<T> {
    /**
     * 發(fā)送消息
     *
     * @param value 發(fā)送的消息
     */
    void post(final T value);

    /**
     * 延遲發(fā)送消息
     *
     * @param value 發(fā)送的消息
     * @param delay 延遲的毫秒數(shù)
     */
    void postDelay(final T value, final long delay);

    /**
     * 延遲發(fā)送,帶生命周期
     * 如果延時發(fā)送消息的時候sender處于非激活狀態(tài)在扰,消息取消發(fā)送
     *
     * @param owner 消息發(fā)送者
     * @param value 發(fā)送的消息
     * @param delay 延遲毫秒數(shù)
     */
    void postDelay(@NonNull final LifecycleOwner owner, final T value, final long delay);

    /**
     * 發(fā)送消息
     * 強(qiáng)制接收到消息的順序和發(fā)送順序一致
     *
     * @param value 發(fā)送的消息
     */
    void postOrderly(final T value);

    /**
     * 注冊一個Observer缕减,帶有生命周期感知,自動取消訂閱
     * 在注冊之前的發(fā)送消息芒珠,在注冊時不會被接收
     *
     * @param owner    LifecycleOwner
     * @param observer 觀察者
     */
    void observe(@NonNull final LifecycleOwner owner, @NonNull final Observer<T> observer);

    /**
     * 注冊一個Observer桥狡,帶有生命周期感知,自動取消訂閱
     * 在注冊之前發(fā)送的消息,在注冊時會被接收(消息同步)
     *
     * @param owner    LifecycleOwner
     * @param observer 觀察者
     */
    void observeSticky(@NonNull final LifecycleOwner owner, @NonNull final Observer<T> observer);

    /**
     * 注冊一個Observer裹芝,需手動解除綁定
     * 在注冊之前的發(fā)送消息呈宇,在注冊時不會被接收
     *
     * @param observer 觀察者
     */
    void observeForever(@NonNull final Observer<T> observer);

    /**
     * 注冊一個Observer,需手動解除綁定
     * 在注冊之前發(fā)送的消息局雄,在注冊時會被接收(消息同步)
     *
     * @param observer 觀察者
     */
    void observeStickyForever(@NonNull final Observer<T> observer);

    /**
     * 通過Forever方式注冊的,需要調(diào)用該方法取消訂閱
     *
     * @param observer 觀察者
     */
    void removeObserver(@NonNull final Observer<T> observer);

    /**
     * 訂閱了此BusObservable的Observer的所在頁面的生命周期狀態(tài)是否一直處于活躍狀態(tài)
     *
     * @param alwaysBeActive {@code true}-Observer可以在Activity的onCreate到onStop之間的生命周期狀態(tài)接收消息
     *                       {@code false}-Observer可以在Activity的onStart到onPause之間的生命周期狀態(tài)接收消息
     */
    BusObservable<T> alwaysBeActive(final boolean alwaysBeActive);

    /**
     * 當(dāng)BusObservable的所有的Observer都被移除時存炮,BusObservable對應(yīng)的Event是否從事件總線中移除
     *
     * @param autoClear
     */
    BusObservable<T> autoClear(final boolean autoClear);
}

四炬搭、LiveBusCore

在進(jìn)行上述一系列鋪墊后,我們可以開始編寫核心的邏輯代碼了穆桂。首先我們先自定義一個類宫盔,命名為LiveBusCore,此類將是我們構(gòu)建的通信框架的核心代碼所在享完。

1. ObserverWrapper

我們在LiveBusCore中灼芭,定義一個內(nèi)部類ObserverWrapper,此類實現(xiàn)了Observer接口般又,主要用于代替LiveData的observe()observeForever()方法中傳入的Observer對象彼绷。ObserverWrapper在調(diào)用數(shù)據(jù)更新方法onChanged(T t)時,在內(nèi)部比Observer多加了一層是否接受更新的判斷茴迁。

private class ObserverWrapper<T> implements Observer<T> {
    @NonNull
    private Observer<T> observer;
    // 是否拒收消息
    private boolean isRejectEvent = false;

    private ObserverWrapper(@NonNull Observer<T> observer) {
        this.observer = observer;
    }

    @Override
    public void onChanged(T t) {
        if (isRejectEvent) {
            isRejectEvent = false;
            return;
        }
        observer.onChanged(t);
    }
}

2. LiveEvent

LiveBusCore中自定義一個內(nèi)部類寄悯,命名為LiveEvent,此類會實現(xiàn)上述提到的BusObservable接口堕义,所以LiveEvent就是我們的被觀察者猜旬,也是我們通信框架中的事件對象。作為一個事件倦卖,其需要持有LiveData洒擦、用于標(biāo)識自己的key以及一個用于存儲自己觀察者的Map。

private class LiveEvent<T> implements BusObservable<T> {
    @NonNull
    private final Object key;
    private final InternalLiveData<T> liveData;
    private final Map<Observer, ObserverWrapper<T>> observerMap;
    private final Handler mainHandler;

    public LiveEvent(@NonNull Object key) {
        this.key = key;
        this.liveData = new InternalLiveData<>();
        this.observerMap = new HashMap<>();
        this.mainHandler = new Handler(Looper.getMainLooper());
    }
    ......
}

2.1 InternalLiveData

雖然之前我們已經(jīng)定義了一個XLiveData怕膛,但是XLiveData只是個基類熟嫩,我們需要按照我們的實際需求再定義一個符合我們需求的LiveData。因為默認(rèn)的LivData的觀察者是在頁面處于onStart()與onPause()之間才能收到消息嘉竟,我們在最大可能的避免內(nèi)存泄漏的情況下邦危,可以增加一個使觀察者接收消息的區(qū)間變?yōu)閛nCreate()與onStop()之間的選擇,方便我們做更多的操作舍扰。

private class InternalLiveData<T> extends XLiveData<T> {
    private boolean observerAlwaysBeActive = false;
    private boolean autoClear = true;

    @Override
    protected Lifecycle.State observerActiveLevel() {
    return observerAlwaysBeActive ? Lifecycle.State.CREATED : Lifecycle.State.STARTED;
    }

    @Override
    public void removeObserver(@NonNull Observer<? super T> observer) {
        super.removeObserver(observer);
        if (autoClear && !liveData.hasObservers()) {
            LiveBusCore.getInstance().mBus.remove(key);
        }
    }
}

InternalLiveData除了重寫了observerActiveLevel()方法倦蚪,還重寫了removeObserver()方法,添加了是否會自動從事件池中移除事件的配置边苹。

2.2 發(fā)送消息

我們在LiveEvent中定義一個發(fā)送消息方法陵且,如下:

@MainThread
private void postInternal(T value) {
    liveData.setValue(value);
}

上述私有方法是直接調(diào)用了LiveData的setValue方法,只能在主線程中調(diào)用,我們還需考慮到在非主線程中調(diào)用的情況慕购,最后形式如下:

@Override
 public void post(final T value) {
    if (isMainThread()) {
        postInternal(value);
    } else {
        mainHandler.post(new PostValueTask(value));
    }
}

private class PostValueTask implements Runnable {
    private Object newValue;

    public PostValueTask(@NonNull Object newValue) {
        this.newValue = newValue;
    }

    @Override
    public void run() {
        postInternal((T) newValue);
    }
}

2.3 普通訂閱

知道了如何發(fā)送消息聊疲,我們再看下如何訂閱消息。在接收消息時沪悲,我們需要使用ObserverWrapper來代替Observer傳遞給LiveData获洲,然后做下判斷,ObserverWrapper只有在liveData.getVersion() > XLiveData.START_VERSION;時殿如,isRejectEvent才為true贡珊;其意義在于觀察者不接收訂閱之前的消息,只接收訂閱后的消息涉馁。

另外门岔,對于訂閱的調(diào)用,我們同樣做了在主線程和非主線程中調(diào)用的處理烤送。

@MainThread
private void observeInternal(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
    ObserverWrapper<T> observerWrapper = new ObserverWrapper<>(observer);
    observerWrapper.isRejectEvent = liveData.getVersion() > XLiveData.START_VERSION;
        liveData.observe(owner, observerWrapper);
}

@Override
public void observe(@NonNull final LifecycleOwner owner, @NonNull final Observer<T> observer) {
    if (isMainThread()) {
        observeInternal(owner, observer);
    } else {
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                observeInternal(owner, observer);
            }
        });
    }
}

2.4 粘性事件

粘性事件是我們在開發(fā)中非常常用的一種事件寒随,LiveData并沒有給我們實現(xiàn),一般都會用observeForever()方法來代替實現(xiàn)帮坚,但是每次訂閱后妻往,我們還必須手動取消訂閱,如果忘記取消试和,可能會引發(fā)內(nèi)存泄漏問題蒲讯,這是非常不友好的。我們此處可以手動實現(xiàn)一下:

@MainThread
private void observeStickyInternal(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
    ObserverWrapper<T> observerWrapper = new ObserverWrapper<>(observer);
    liveData.observe(owner, observerWrapper);
}

 @Override
public void observeSticky(@NonNull final LifecycleOwner owner, @NonNull final Observer<T> observer) {
    if (isMainThread()) {
        observeStickyInternal(owner, observer);
    } else {
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                observeStickyInternal(owner, observer);
            }
        });
    }
}

可以看到灰署,observeStickyInterna()方法與observeInternal()方法基本相同判帮,唯一的區(qū)別就是observeStickyInterna()中未對observerWrapperisRejectEvent屬性做處理,即觀察者可以收到訂閱之前的消息溉箕。

2.5 removeObserver

移除觀察者就比較容易了晦墙,只需要在移除LiveData的觀察者時,同時從LiveEvent的觀察者集合中移除掉對應(yīng)的觀察者肴茄。

@MainThread
private void removeObserverInternal(@NonNull Observer<T> observer) {
    Observer<T> realObserver;
    if (observerMap.containsKey(observer)) {
        realObserver = observerMap.remove(observer);
    } else {
        realObserver = observer;
    }
    liveData.removeObserver(realObserver);
}

@Override
public void removeObserver(@NonNull final Observer<T> observer) {
    if (isMainThread()) {
        removeObserverInternal(observer);
    } else {
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                removeObserverInternal(observer);
            }
        });
    }
}

3. 單例

上述內(nèi)容已經(jīng)介紹了消息發(fā)送晌畅,普通訂閱、粘性事件以及移除觀察者寡痰,在我們定義的被觀察者接口中還有延遲發(fā)送消息抗楔、永久訂閱、永久粘性訂閱等方法拦坠,這些方法與我們上述的實現(xiàn)都是大同小異连躏,原理都是相同的,我們此處就不再贅述贞滨。最后我們看下單例的實現(xiàn):

public class LiveBusCore {

    public static LiveBusCore getInstance() {
        return Holder.DEFAULT_BUS;
    }

    private static class Holder {
        private static final LiveBusCore DEFAULT_BUS = new LiveBusCore();
    }

    private Map<Object, LiveEvent> mBus;

    private LiveBusCore() {
        this.mBus = new HashMap<>();
    }

    public synchronized <T> BusObservable<T> with(Object key, Class<T> eventType) {
        if (!mBus.containsKey(key)) {
            mBus.put(key, new LiveEvent<T>(key));
        }
        return (BusObservable<T>) mBus.get(key);
    }

    public synchronized void removeEvent(@NonNull Object key) {
        if (!mBus.containsKey(key)) {
            mBus.remove(key);
        }
    }
    ......
}

為了方便使用入热,我們可以再封裝一層:

public final class LiveBus {

    public static <T> BusObservable<T> get(@NonNull Object key, Class<T> type) {
        return LiveBusCore.getInstance().with(key, type);
    }

    public static <T> BusObservable<T> get(@NonNull Class<T> eventType) {
        return LiveBusCore.getInstance().with(eventType.getName(), eventType);
    }

    public static BusObservable<Object> get(@NonNull Object key) {
        return LiveBusCore.getInstance().with(key, Object.class);
    }

    public static void removeEvent(@NonNull Object key) {
        LiveBusCore.getInstance().removeEvent(key);
    }
}

使用示例:

// 發(fā)送消息
LiveBus.get(Constants.LK_TEST2_POST).post("收到來自Test2的消息")

// 接收消息
LiveBus.get(Constants.LK_TEST2_POST, String::class.java)
    .observe(this, object : Observer<String> {
        override fun onChanged(t: String?) {
            viewModel.normalObserveData.value = "普通訂閱:${t}"
        }
    })

五、小結(jié)

使用LiveData搭建通信框架的講解就到此結(jié)束了,有興趣的同學(xué)可以去我的Github上下載源碼勺良,項目是 Fly-Android(https://github.com/albert-lii/Fly-Android)绰播。如果在項目中沒有跨進(jìn)程通信等特殊需求的話,建議自己使用LiveData搭建通信框架尚困,這樣更加靈活蠢箩,易修改。最后給出美團(tuán)的框架:LiveEventBus事甜。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忙芒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子讳侨,更是在濱河造成了極大的恐慌,老刑警劉巖奏属,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跨跨,死亡現(xiàn)場離奇詭異,居然都是意外死亡囱皿,警方通過查閱死者的電腦和手機(jī)勇婴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘱腥,“玉大人耕渴,你說我怎么就攤上這事〕萃茫” “怎么了橱脸?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長分苇。 經(jīng)常有香客問我添诉,道長,這世上最難降的妖魔是什么医寿? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任栏赴,我火速辦了婚禮,結(jié)果婚禮上靖秩,老公的妹妹穿的比我還像新娘须眷。我一直安慰自己,他們只是感情好沟突,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布花颗。 她就那樣靜靜地躺著,像睡著了一般惠拭。 火紅的嫁衣襯著肌膚如雪捎稚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音今野,去河邊找鬼葡公。 笑死,一個胖子當(dāng)著我的面吹牛条霜,可吹牛的內(nèi)容都是我干的催什。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宰睡,長吁一口氣:“原來是場噩夢啊……” “哼蒲凶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拆内,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旋圆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后麸恍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灵巧,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年抹沪,在試婚紗的時候發(fā)現(xiàn)自己被綠了刻肄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡融欧,死狀恐怖敏弃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情噪馏,我是刑警寧澤麦到,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站欠肾,受9級特大地震影響隅要,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜董济,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一步清、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧虏肾,春花似錦廓啊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吹埠,卻和暖如春第步,著一層夾襖步出監(jiān)牢的瞬間疮装,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工粘都, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廓推,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓翩隧,卻偏偏與公主長得像樊展,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子堆生,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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