Android不使用反射,完成LiveDataBus

LiveDataBus大家都很熟悉了,网上也有很多通过反射实现的LiveDataBus。但是通过反射实现的代码比较混乱,也比较难以理解。这里给出一版通过代码实现的。更加的简洁优雅~

首先来看一下LiveData原理

一般我们都是这样使用的,创建一个LiveData去发送数据,在你想观察的地方去注册。这样只要数据发射,你就能拿到你想要的数据了。


image.png

下面就是你再使用红框语句时的调用流程


image.png

所以首先进入 observe 方法看一看

image.png

这样我们创建的LifecycleBoundObserver(observe方法中的new Observer
就和宿主(observe方法中传入的this) 建立了联系。

所以宿主每次生命周期的变化都会调用到
LifecycleBoundObserver的onStateChanged
而从代码中也可以看到,在宿主生命周期是DESTROYED时,会主动移除掉当前mObserver,完成自动反注册,这里注意要把mObservers 和 mObserver分清楚

image.png

这里有几点需要注意一下
1.LiveData中的mObservers是一个Map,还有一个mVersion字段默认等于 -1


image.png

2.LifecycleBoundObserver 继承 ObserverWrapper
里面有mObserver,其实就是保存自己
还有一个mLastVersion字段,默认等于 -1


image.png

接下来继续进入activeStateChanged方法,其他方法不多解释。
这里直接进入dispatchingValue
可以看到dispatchingValue不管走哪边都会进入considerNotify


image.png
image.png

接下来看considerNotify


image.png

看到这里我相信你已经知道,我们为啥能再onChanged拿到数据了

    viewModel.liveDataObject.observe(this, new Observer<Bean>() {
        @Override
        public void onChanged(Bean data) {
              接收数据
        }
    });

接下来看一下,postValue和setValue

可以看到setValue是有注解MainThread的,表示只能在主线程中使用
而postValue没有,把某事件抛到主线程去了


image.png

再来来看一下,postValue在切换到主线程中都干了些啥,我们发现他的Runnable中的方法,最终还是执行了setValue。


image.png

所以这样看
postValue只不过是可以在子线程执行,但是消息发送最终还是要到主线程,且执行setValue
而setValue就只能在主线程执行了

在执行了setValue或者postValue后,mVersion+1,接着直接进入到considerNotify


image.png

黏性事件怎么来的?

为了造成黏性事件,我再注册观察者之前就将数据发送出去,然后通过按钮点击再去注册一个观察者,我们能发现,即使是之前发送的数据,仍然能够接受得到,这就是黏性事件。


image.png

造成的原因就是mLastVersion 和 mVersion

image.png

实现自己的LiveDataBus

LiveData基本的都了解过了,接下来自己实现一个,既可以接受黏性事件,又可以接受普通事件。

怎么控制黏性事件

其实原本的代码就是可以发送事件的,只不过不能自由的控制黏性事件,
**如果我们能用一个变量去标志就好了,比如这样标志一个receiveSticky变量**
为true就是接受黏性事件,那么调用方法发送数据
为false的话就会,跳过此方法


if (observer.mLastVersion >= mVersion) { 
     return;
}
observer.mLastVersion = mVersion;
if(receiveSticky){
     observer.mObserver.onChanged((T) mData);
} else {
     // 处理普通事件
}

在假设我们能直接在源码添加这个字段的话,那这个receiveSticky从哪里来呢?
1.发送者的角度:从 postValue 和 setValue 入手
比如改写成 postValue(data,receiveSticky)
这样有个弊端,这样只能统一发送黏性或者非黏性,这样如果多个宿主监听同一个消息,而有些需要黏性,有些不需要,这样就很难控制

2.从接收者的角度:从observer入手
我们知道我们传入的observer,在包装成LifecycleBoundObserver后,才有mLastVersion。那我们可以参考一下这种思路

比如:LifecycleBoundObserver包装一下,有了mLastVersion
那么:我们将传入的Observer也包装一层,在创建的时候传入receiveSticky就好了
就像这样:(当然这不是完整版,这只是记录一下思路)

image.png

怎么保证接收的是同一个事件

        liveData.postValue
        viewModel.liveData.observer(this , Observer {

        })

一般我们都是这样发送接收的,这个LiveData都是同一个才能接收同一份数据。
所以我们也必须在LiveDataBus保证是同一个LiveData才行。

还是同样的思路,要区分LiveData,就给LiveData加名字就行了呗
那我就再给LiveData包装一层,让调用者传入名字去生成
生成完了就保存下来,以后就用名字去找到对应的LiveData
就好比:

image.png

那么他的用法就是这样的:接收消息的有点过于复杂了。


image.png

既然observer是LiveData里面的方法,
而每次发送消息时间都是StickyObserver(sticky, Observer())
这样我们就可以在我们的包装类中去复写一下observer,比如:

image.png

这样发送接收数据就会变成,比之前稍微好一些


image.png

如何解决普通事件的接收

在上面我们其实没对普通事件做处理
我们通过sticky能判断接不接受黏性事件
但是我们不知道在我们注册之前,有没有消息事件发送

override fun onChanged(t: T) {
    if (sticky) {
       observer.onChanged(t)
    } else {
        // 普通事件
    }
}

回想一下,黏性事件是怎么产生的?
简单的认为,observer.mLastVersion(Observer的) < mVersion(LiveData的) 就会产生黏性事件
所以我们也可以模仿一下,弄两个变量去判断


image.png

在我们的LiveDate中


image.png

在我们的observe中会被改写成这样


image.png

所以显然不能完全完全按照源码照抄
那黏性事件之所以会被发送出去,
就是在StickyObserver初始化时mLastVersionmLiveDataVersion没对齐,
导致if (mLastVersion >= stickyLiveData.mLiveDataVersion) {} 没进入
所以进入if条件就有黏性事件,所以我们要改成这样

image.png

最后附上整个代码,直接复制就可以用


object LiveDataBus {

//    LiveDataBus.with<String>("TestLiveDataBus").postStickyData("测试!")
//    LiveDataBus.with<String>("TestLiveDataBus") .observerSticky(this, false) {
//
//    }

    private val mStickyMap = ConcurrentHashMap<String, StickyLiveData<*>>()

    fun <T> with(eventName: String): StickyLiveData<T> {
        var stickyLiveData = mStickyMap[eventName]
        if (stickyLiveData == null) {
            stickyLiveData = StickyLiveData<T>(eventName)
            mStickyMap[eventName] = stickyLiveData
        }

        return stickyLiveData as StickyLiveData<T>
    }


    /**
     * 将发射出去的LiveData包装一下,再做一些数据保存
     */
    class StickyLiveData<T>(private var eventName: String) : LiveData<T>() {

        var mLiveDataVersion = 0
        var mStickyData: T? = null

        fun setStickyData(stickyData: T) {
            mStickyData = stickyData
            setValue(stickyData)
        }

        fun postStickyData(stickyData: T) {
            mStickyData = stickyData
            postValue(stickyData)
        }

        override fun setValue(value: T) {
            mLiveDataVersion++
            super.setValue(value)
        }

        override fun postValue(value: T) {
            super.postValue(value)
        }

        fun observerSticky(owner: LifecycleOwner, sticky: Boolean, observer: Observer<in T>) {
            // 移除自己保存的StickyLiveData
            owner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
                if (event == Lifecycle.Event.ON_DESTROY) {
                    mStickyMap.remove(eventName)
                }
            })

            super.observe(owner, StickyObserver(this, sticky, observer))
        }

        /**
         * 重写LiveData的observer,把传入的observer包装一下
         */
        override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
            observerSticky(owner,false, observer)
        }
    }

    class StickyObserver<T>(
        private val stickyLiveData: StickyLiveData<T>,
        private val sticky: Boolean,
        private val observer: Observer<in T>
    ) : Observer<T> {

        /**
         * 打个比方:
         * 一条数据,名称为TestName,
         *      对应一个 StickyLiveData, 也就对应一个version, 初始的值为0,且这个可以复用
         *      且会创建StickyObserver,对应一个 mLastVersion, 初始的值为0
         *
         * 如果 StickyLiveData#version 和 StickyObserver#mLastVersion 没有对齐
         *      LastVersion < version --> 直接发送数据,就会产生黏性事件
         *
         * 源码就是这样没对齐,所以无法控制黏性事件
         *
         * 因为源码的流程
         *      将传入的observer包装成LifecycleBoundObserver(继承ObserverWrapper)会将传入的observer做保存和保存在hashMap
         *      最后在considerNotify遍历hashMap,活跃的观察者会调用observer.onChanged(t)去发送数据
         *
         * 所以这里把传入的observer包装成StickyObserver 进入源码后 --> 再变成LifecycleBoundObserver
         * 所以最终发送数据会调用StickyObserver的onChanged 就可以做黏性事件的处理了
         *
         */
        private var mLastVersion = stickyLiveData.mLiveDataVersion

        override fun onChanged(t: T) {

            if (mLastVersion >= stickyLiveData.mLiveDataVersion) {
                if (sticky && stickyLiveData.mStickyData != null) {
                    observer.onChanged(stickyLiveData.mStickyData)
                }
                return
            }
            observer.onChanged(t)
        }
    }


}


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容