深入理解AAC架構 - LiveDataBus 基于LiveData實現(xiàn)的事件總線

LiveData作為AAC架構的關鍵模塊之一秃臣,具有優(yōu)秀的生命周期感知特性。
本身采用觀察者模式诗轻,由于其生命周期感知特性雳窟,可以用來實現(xiàn)事件總線。

本文主要內容:

  • 基本思路:基本的實現(xiàn)思路
  • 粘性事件特性分析:LiveData觀察時推最新數據引發(fā)的問題
  • 解決思路:解決思路和引入粘性事件

本文主要采用反射對Version進行管理魄梯,使用Class作為消息管理。
也可以使用其他實現(xiàn)方案,或在參考后自建方案:

本文源碼直接拷貝即可使用。
也可以前往GitHub倉庫查看下載:LiveDataBus (采用反射+包裝類的形式)


基本思路:

  • observe()
    即調用LiveData.observe()故源,基于LiveData的生命周期特性自動管理觀察者污抬。
  • observeForever()
    即調用LiveData.observeForever(),對無法提供LifecycleOwner的觀察者進行支持绳军。
  • removeObserver()
    即調用LiveData.removeObserver()印机,移除對應觀察者。
  • postMessage()
    發(fā)布任意類型的消息门驾,消息會通知所有關注此類型的觀察者射赛。
object LiveDataBus {
    private val mBus: MutableMap<String, MutableLiveData<*>> = hashMapOf()

    fun <T : Any> observe(channelClass: Class<T>, owner: LifecycleOwner, observer: Observer<T>) {
        getChannel(channelClass).observe(owner, observer)
    }

    fun <T : Any> observeForever(channelClass: Class<T>, observer: Observer<T>) {
        getChannel(channelClass).observeForever(observer)
    }

    fun <T : Any> removeObserver(channelClass: Class<T>, observer: Observer<T>) {
        getChannel(channelClass).removeObserver(observer)
    }

    fun <T : Any> postMessage(message: T) {
        getChannel(message::class.java as Class<T>).postValue(message)
    }

    private fun <T : Any> getChannel(channelClass: Class<T>): MutableLiveData<T> {
        val channelName = channelClass.simpleName
        if (!mBus.containsKey(channelName)) {
            mBus[channelName] = MutableLiveData<T>()
        }
        return mBus[channelName] as MutableLiveData<T>
    }
}

使用方式:

// 定義信息
class Message

// 訂閱信息
LiveDataBus.observe(Message::class.java, mAnyLifecycleOwner, Observer { })
LiveDataBus.observeForever(Message::class.java, Observer { })

// 發(fā)布信息
LiveDataBus.postMessage(Message())

粘性事件問題

網上很多LiveDataBus的實現(xiàn)都有對該問題進行分析,主要源自LiveData的實時通知觀察者特性奶是,這個原來相當優(yōu)秀的機制楣责,在用來實現(xiàn)LiveDataBus的時候竣灌,反而會引發(fā)觀察時收到舊消息的問題。

解決方法并不復雜秆麸,通過自定義BusLiveData繼承MutableLiveData初嘹,并重寫observe()observeForever()

  • observe()
    用于通過observe()加入的observer沮趣,通過反射屯烦,使ObserverWrapper維護的數據版本等于當前LiveData數據版本。
    在觸發(fā)生命周期回調時房铭,由于數據版本相同漫贞,系統(tǒng)判斷為已通知,即不會觸發(fā)數據更新育叁。
  • observeForever()
    使通過observeForever()新加入的observer迅脐,需要屏蔽所有通過observeForever()發(fā)起的onChanged()調用。
    通過把傳入的observer通過一個自定義的ObserverWrapper裝修類豪嗽,在onChanged()判斷當前調用棧是否有observeForever()谴蔑,存在時則不觸發(fā)實際的observer.onChanged()

最終方案

最后修改后的LiveDataBus

/**
 * LiveDataBus
 * 基于LiveData實現(xiàn)的事件總線
 */
object LiveDataBus {
    private val mBus by lazy { mutableMapOf<String, BusLiveData<*>>() }

    fun <T : Any> observe(channel: Class<T>, owner: LifecycleOwner, observer: Observer<T>) {
        getChannel(channel).observe(owner, observer)
    }

    fun <T : Any> observeSticky(channel: Class<T>, owner: LifecycleOwner, observer: Observer<T>) {
        getChannel(channel).observeStick(owner, observer)
    }

    fun <T : Any> observeForever(channel: Class<T>, observer: Observer<T>) {
        getChannel(channel).observeForever(observer)
    }

    fun <T : Any> observeStickyForever(channel: Class<T>, observer: Observer<T>) {
        getChannel(channel).observeStickyForever(observer)
    }

    fun <T : Any> removeObserver(channelClass: Class<T>, observer: Observer<T>) {
        getChannel(channelClass).removeObserver(observer)
    }

    fun <T : Any> postMessage(message: T) {
        getChannel(message::class.java as Class<T>).postValue(message)
    }

    private fun <T : Any> getChannel(channelClass: Class<T>): BusLiveData<T> {
        return mBus.getOrPut(channelClass.simpleName) {
            BusLiveData<T>()
        } as BusLiveData<T>
    }

    /**
     * 自定義的LiveData
     *
     * 用于通過`observe()`加入的`observer`
     * 通過反射龟梦,使`ObserverWrapper`維護的數據版本等于當前`LiveData`數據版本隐锭。
     * 在觸發(fā)生命周期回調時,由于數據版本相同计贰,系統(tǒng)判斷為已通知钦睡,即不會觸發(fā)數據更新。
     *
     * 使通過`observeForever()`新加入的`observer`
     * 需要屏蔽所有通過`observeForever()`發(fā)起的`onChanged()`調用躁倒。
     * 通過把傳入的`observer`通過一個自定義的`ObserverWrapper`裝修類荞怒。
     * 在`onChanged()`判斷當前調用棧是否有`observeForever()`,存在時則不觸發(fā)實際的`observer.onChanged()`秧秉。
     */
    private class BusLiveData<T> : MutableLiveData<T>() {

        // 反射緩存
        private companion object {
            private const val fieldObservers = "mObservers"
            private const val methodGet = "get"
            private const val fieldLastVersion = "mLastVersion"
            private const val fieldVersion = "mVersion"
            private val mCache = hashMapOf<String, Any>()
        }

        private val mRealMap = hashMapOf<Observer<*>, Observer<*>>()

        override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
            super.observe(owner, observer)
            setObserverVerToLiveDataVer(observer)
        }

        fun observeStick(owner: LifecycleOwner, observer: Observer<in T>) {
            super.observe(owner, observer)
        }

        override fun observeForever(observer: Observer<in T>) {
            super.observeForever(mRealMap.getOrPut(observer) {
                WrapperObserver(observer)
            } as Observer<in T>)
        }

        fun observeStickyForever(observer: Observer<in T>) {
            super.observeForever(observer)
        }

        override fun removeObserver(observer: Observer<in T>) {
            if (mRealMap.containsKey(observer)) {
                super.removeObserver(mRealMap.remove(observer) as Observer<T>)
            } else {
                super.removeObserver(observer)
            }
        }

        private fun setObserverVerToLiveDataVer(observer: Observer<in T>) {
            val liveDataClass = LiveData::class.java
            try {
                val mObserversField: Field = mCache.getOrPut(fieldObservers) {
                    val field = liveDataClass.getDeclaredField(fieldObservers)
                    field.isAccessible = true
                    field
                } as Field
                val mObservers = mObserversField[this]

                val getMethod = mCache.getOrPut(methodGet) {
                    val method = mObservers.javaClass.getDeclaredMethod(methodGet, Any::class.java)
                    method.isAccessible = true
                    method
                } as Method

                val boundObserverEntry = getMethod.invoke(mObservers, observer)
                var boundObserver: Any? = null
                if (boundObserverEntry is Map.Entry<*, *>) {
                    boundObserver = boundObserverEntry.value
                }
                if (boundObserver == null) {
                    throw NullPointerException("LifecycleBoundObserver cant be null")
                }

                val mLastVersionField = mCache.getOrPut(fieldLastVersion) {
                    val wrapperClass: Class<in Any> = boundObserver.javaClass.superclass
                            ?: throw NullPointerException("Cant access ObserverWrapper.class")
                    val field = wrapperClass.getDeclaredField(fieldLastVersion)
                    field.isAccessible = true
                    field
                } as Field

                val mVersionField = mCache.getOrPut(fieldVersion) {
                    val field = liveDataClass.getDeclaredField(fieldVersion)
                    field.isAccessible = true
                    field
                } as Field

                mLastVersionField.set(boundObserver, mVersionField[this])
            } catch (e: Exception) {
            }
        }
    }

    /**
     * Observer包裝類
     */
    private class WrapperObserver<T>(
            private val observer: Observer<T>
    ) : Observer<T> {

        private companion object {
            private const val clazz = "android.arch.lifecycle.LiveData"
            private const val func = "observeForever"
        }

        override fun onChanged(t: T) {
            if (!isCallOnObserve()) {
                observer.onChanged(t)
            }
        }

        private fun isCallOnObserve(): Boolean {
            for (element in Thread.currentThread().stackTrace) {
                if (clazz == element.className && func == element.methodName) {
                    return true
                }
            }
            return false
        }
    }
}

使用方式:

// 定義信息
class Message

// 訂閱信息
LiveDataBus.observe(Message::class.java, this, Observer { })
LiveDataBus.observeForever(Message::class.java, Observer { })

// 訂閱粘性信息
LiveDataBus.observeSticky(Message::class.java, this, Observer { })
LiveDataBus.observeStickyForever(Message::class.java, Observer { })

// 發(fā)布信息
LiveDataBus.postMessage(Message())
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末褐桌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子象迎,更是在濱河造成了極大的恐慌荧嵌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砾淌,死亡現(xiàn)場離奇詭異啦撮,居然都是意外死亡,警方通過查閱死者的電腦和手機汪厨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門赃春,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骄崩,你說我怎么就攤上這事聘鳞”「ǎ” “怎么了要拂?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵抠璃,是天一觀的道長。 經常有香客問我脱惰,道長搏嗡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任拉一,我火速辦了婚禮采盒,結果婚禮上,老公的妹妹穿的比我還像新娘蔚润。我一直安慰自己磅氨,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布嫡纠。 她就那樣靜靜地躺著烦租,像睡著了一般。 火紅的嫁衣襯著肌膚如雪除盏。 梳的紋絲不亂的頭發(fā)上叉橱,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音者蠕,去河邊找鬼窃祝。 笑死,一個胖子當著我的面吹牛踱侣,可吹牛的內容都是我干的粪小。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼抡句,長吁一口氣:“原來是場噩夢啊……” “哼糕再!你這毒婦竟也來了?” 一聲冷哼從身側響起玉转,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤突想,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后究抓,有當地人在樹林里發(fā)現(xiàn)了一具尸體猾担,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年刺下,在試婚紗的時候發(fā)現(xiàn)自己被綠了绑嘹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡橘茉,死狀恐怖工腋,靈堂內的尸體忽然破棺而出姨丈,到底是詐尸還是另有隱情,我是刑警寧澤擅腰,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布蟋恬,位于F島的核電站,受9級特大地震影響趁冈,放射性物質發(fā)生泄漏歼争。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一渗勘、第九天 我趴在偏房一處隱蔽的房頂上張望沐绒。 院中可真熱鬧,春花似錦旺坠、人聲如沸乔遮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹋肮。三九已至,卻和暖如春蝉衣,著一層夾襖步出監(jiān)牢的瞬間括尸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工病毡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留濒翻,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓啦膜,卻偏偏與公主長得像有送,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子僧家,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345