学穿:LiveData → ???

638a9a2326983b28dc2756239c108b01.png

/   今日科技快讯   /

近日,有记者就中国企业小米称印度执法机构搞极端胁迫一事向发言人提问。对此,发言人赵立坚表示,中方正密切关注此事。中国政府一贯要求中国企业在海外合法合规经营,同时坚定支持中国企业维护自身合法权益。中方希望印方为中国企业在印投资经营提供公平、公正、非歧视的营商环境,依法合规调查执法,提振国际投资的信心。

/   作者简介   /

本篇文章来自coder_pig的投稿,文章主要分享了他对LiveData的探索分析,相信会对大家有所帮助!

coder_pig的博客地址:

https://juejin.cn/user/4142615541321928

/   引言   /

在开始这篇文章前,我就遇到了第一个关于LiveData的问题:该怎么翻译这个词呢?

81e1e3d5d13a3ef5d14efcfb3d72c2fc.png

  • 活的数据?→ 有点奇怪,难不成还有死的数据?

  • 可观察数据?→ 感觉跟Observable有点沾边了

  • 生命周期感知数据?→ 名字也忒长了...

自己想不到,那就集思广益,尝试向群里的小伙伴发起咨询。

6395f244b50d12355c7bfbbdcec4d3e3.png

好像也没找到比较好的翻译 (好像越来越可刑),索性就不翻了,哈哈哈~

LiveData一言以蔽之,LiveData是能感知生命周期的、可观察的、粘性的数据持有者,以数据驱动的方式更新界面。

感知谁的生命周期?

具有生命周期感知的组件,一般代指Activity、Fragment,但不局限此,更泛指实现了Lifecycle.LifecycleOwner的组件。

如:利用Lifecycle让App进程拥有生命周期感知 → ProcessLifecycleOwner。

简单点说,实现了Lifecycle那一套的组件,Activity、Fragment默认实现了,所以可以直接用。

怎么感知生命周期?

  • LifecycleOwner提供了一个getLifecycle()用于获取Lifecycle实例;

  • 你只需自定义一个观察者实现LifecycleEventObserver或DefaultLifecycleObserver,调用Lifecycle实例的addObserve()添加观察者实例即可。

  • 当组件生命周期发生变化,就会通知所有的观察者(回调方法)。

LiveData里,还对观察者包了一层 → LifecycleBoundObserver。

014b653b2a0ec8a80e46a2d354c01276.png

图中圈住的①,当组件处于DESTORYED状态时,自动移除观察者,这样的好处,避免内存泄露。

图中圈住的②,点开activeStateChanged()方法:

310a1d31c24a1dc69c6f126acdb10a2b.png

当组件处于STARTED/RESUMED状态,才发送数据,这样的好处是避免因组件处于非活跃状态,接收LiveData事件导致的崩溃,最常见的就是Activity停止了,还执行相关回调。

可观察

除了LifecycleBoundObserver生命周期观察者外,LiveData内部还提供了一个数据观察者Observer。

8aa00f69798ed0521be612d325893c55.png

可以调用observe()添加数据观察者。

d22b6246866bf8a16cc2039af7a7ac24.png

在dispatchingValue()分发事件,considerNotify()中回调onChanged(),完成数据更新。

66640ea0af37037c42968b4990719141.png


粘性

所谓的粘性,通俗点说,先发送数据,然后订阅,也可以收到之前发的数据。在LiveData中的表现,新的观察者注册时,会收到注册前分发的值。多说无益,不如来个栗子实在。

// 定义变量
private val nNum = MutableLiveData(0)

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    // 绑定第一个观察者
    nNum.observe(this) { Log.e("Test", "第一个观察者:${nNum.value}") }
    // 更新值
    nNum.setValue(100)

    // 延迟1000ms后执行
    mBinding.btTest.postDelayed({
        // 添加新观察者
        nNum.observe(this) { Log.e("Test", "第二个观察者:${nNum.value}") }
    },1000L)
}

运行后输出日志如下:

7f24fad01077834abd164ddf9134236d.png

现象:第二个观察者刚注册,就收到分发的值。这是咋回事?

4800fe8532eda83beab1fd82d9ed6217.png

不急不急,先来探一探 值分发的原理,首先,要实现粘性,得把值存起来 吧!跟下构造方法:

9c34a4aed53c53657513d6d63ad37eca.png

不难看出一丝端倪:

  • 用一个 Object 类型的 mData 存值;

  • 用一个 int 类型的 mVersion 来标识值的版本,初始值-1,如果版本号小于最新版本号,表示当前的值要更新。

跟下更新值的方法:

e4a4fb4e81877b7da78fe6f1c93d58a1.png

新值覆盖旧值,使值的版本号自增1,值分发(遍历所有观察者并分发)。

8e8fdba77dc4aa57194049b17cd54098.png

ecb6820091fb36e9c57a0e2a51f84fc9.png

观察者中也持有一个值的版本号,ObserverWrapper包Observer带的私货:

91b12cccf7fb69ceae6fce9a7bd6efe7.png

这样设计的好处,数据始终保持最新状态。

值发生变化后遍历所有观察者,看到这里,读者可能有这样的疑问:

生命周期组件处于非活跃状态,如Activity切到后台,此时发生数据变化,然后切回前台,又是怎么更新的?

留意下面的代码:

19eda3155a40c6ea78ed8e8d0646f3bc.png

当生命周期组件发生状态流转,就会回调onStateChanged,Activity从后台到前台自然会走这里,完成数据更新。注意:这里是单独分发给和生命周期组件绑定的观察者,不是群发!!!

总结下,值变更 + 生命周期组件状态变更都会引起值分发。

9a66a96cff0a1a5e1946807df464f3ce.png

到此,好像也没定位到引起粘性的原因?嗯,还得再跟一下,LiveData.observe() → owner.getLifecycle().addObserver(wrapper) → LifecycleRegistry.addObserver()

feb898a7949d896d3b90715e9ff0dea8.png

Lifecycle添加观察者,状态流转,调用dispatchEvent()方法完成事件分发:

0e73086858257f40908cf39e71138c2a.png

a3aaf8519af2c5ed6d4fc8a2057fcff1.png

回调了onStateChanged()方法,符合第二种场景 → 生命周期组件状态变更:

b16ee4f45b527909104758686e5e0e38.png

activeStateChanged() → dispatchingValue() → considerNotify():

394e1d930e5cedf66d6c124d5890bbd5.png

粘性的原因到此就一清二楚了,此处应有掌声!!!

583b2cd7d57b5612ef8edf49599c7832.png

怎么解决粘性问题?不急,等下讲,这里TM才开头而已,LiveData用法都还没介绍呢。

数据驱动

简单点说:不需要主动给界面推(设置数据),界面会被动观察数据变化。

/   LiveData使用详解   /

老规矩,官方文档双手奉上:《LiveData 概览》,以官方文档和源码为准~

依赖组件

LiveData基本配着ViewModel一起用的,你实在不想依赖,可以按官方文档的来。

def lifecycle_version = 2.4.1

// Java项目
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

// Kotlin项目
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

咳,如果你启用了DataBinding,可以不用另外依赖,不然你会发现两个版本的livedata:

b6e9623d320a898995ba6604aeda581a.png

命令行键入:gradlew :app:dependencies > dependencies.txt 扫一波就知道了:

0adf5670644e27a8fd4480f7c807015e.png



创建、观察、更新

创建就不用说了,LiveData是抽象类,要么继承自定义,要么用 MutableLiveData。

观察、更新在粘性那里的例子已经演示得很清楚了,就不Copy了,这里提一嘴添加观察者的两个方法:

  • observe() → 当生命周期组件处于STARTED和RESUMED状态下才会收到分发值。

  • observeForever() → 跟生命周期组件状态没关系了,都会收到分发值,要手动调 removeObserver() 移除!

还有两种改变数据的方法:

  • setValue() → 只能在主线程中调用;

  • postValue() → 既可以在主线程中调用,也可以在子线程中调用,最终调用的还是setValue();


为什么LiveData搭配ViewModel使用?

1、避免Activity、Fragment的代码臃肿,不塞ViewModel里也要做数据和组件分离;

2、将LiveData与特定的Activity、Fragment分离,配置发生更改(如旋转)导致重建,不影响LiveData。


LiveData扩展

就是LiveData预留了两个回调,onActive() 和 onInactive(),当生命周期组件在活跃和非活跃状态间切换时会回调。

01d8ae7e8b03f82df2023bb2343f0131.png

可以怎么玩?比如写一个全局可用的倒计时器,先写倒计时相关功能。

object CountDownManager {
    private var mRemainSecond: Long = 10L
    private var mTimer: CountDownTimer? = null
    private var mListener = arrayListOf<CountDataChangeListener>()

    // 开始倒计时
    fun startCount(remainSecond: Long? = 10L) {
        mRemainSecond = remainSecond!!
        mTimer = object: CountDownTimer(remainSecond * 1000, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                mRemainSecond--
                dispatchMessage("剩余:$mRemainSecond 秒")
            }

            override fun onFinish() {
                dispatchMessage("倒计时结束")
            }
        }
        mTimer!!.start()
    }

    // 取消倒计时
    fun cancelCount() {
        if(mTimer != null) {
            mTimer!!.cancel()
            mListener.clear()
        }
    }

    // 遍历回调方法
    private fun dispatchMessage(msg: String) { mListener.forEach { it.onChange(msg) } }

    // 添加监听器
    fun setListener(listener: CountDataChangeListener) { mListener.add(listener) }

    // 移除监听器
    fun removeListener(listener: CountDataChangeListener) { mListener.remove(listener) }
}

// 回调接口
interface CountDataChangeListener{
    fun onChange(msg: String)
}

非常简单,就是初始化倒计实例,遍历回调而已,接着继承LiveData,写一个全局数据类。

class GlobalLiveData : LiveData<String>() {
    companion object {
        private lateinit var instance: GlobalLiveData
        fun getInstance() = if (::instance.isInitialized) instance else GlobalLiveData()
    }

    private val mListener = object : CountDataChangeListener {
        override fun onChange(msg: String) {
            postValue(msg)  // 调用更新数据的方法
        }
    }

    fun startCount(remainSecond: Long? = 10L) { CountDownManager.startCount(remainSecond) }

    fun cancelCount() { CountDownManager.cancelCount() }

    // 活跃时添加监听器
    override fun onActive() {
        super.onActive()
        CountDownManager.setListener(mListener)
    }

    // 不活跃时移除监听器
    override fun onInactive() {
        super.onInactive()
        CountDownManager.removeListener(mListener)
    }
}

然后是调用处。

// 添加倒计时监听
GlobalLiveData.getInstance().observe(this) { mBinding.tvTest.text = it }
// 开始倒计时
GlobalLiveData.getInstance().startCount(30)

你只管添加观察,无需担心移除,页面(Activity、Fragment)不活跃时不更新页面,活跃时才更新。运行效果如下:

eb638dbadac52509deb7bdf736e416df.gif

Tips:只是写下例子,真正用到项目还得自己改下哈,如添加/移除观察者时加锁 ~

LiveData转换

Lifecycle包提供了Transformations来对LiveData的数据类型进行转换,可以 在数据返回给观察者前,修改LiveData中数据的具体类型。

Transformations中有两个常用的转换方法map()和swtichMap(),它们都是使用MediatorLiveData作为数据的中间消费者,并将转换后的数据传递给最终消费者。


Transformations.map()

先看下map()的具体实现:

233e417e0e368f9cedc915ea5415e275.png

流程解读:

  • 实例化一个MediatorLiveData实例result;

  • 调用addSource()传入源LiveData和新建的Observer实例;

  • 在Observer的回调方法onChange()中,把转换函数的执行结果传入result的setValue()方法中;

  • 最后返回result;

跟下MediatorLiveData.addSource()。

4ac6e73c437d5d6609e0641821f48700.png

用Source包了一层,将传入参数关林,直接跟Source.plug()。

ada376ffe2281beb6dd19e5ddedbaa56.png

吼,就是给源LiveData添加了一个观察者,当它发生数据变化时更新新LiveData的值。写个简单例子:

// 源LiveData
val mUserLiveData = MutableLiveData<User>()
mUserLiveData.observe(this) {
    Log.e("Test", "mUserLiveData更新了:${it.javaClass.simpleName} --- $it") 
}

// 转换后的LiveData
val mUserNameLiveData = Transformations.map(mUserLiveData) { 
        user -> "${user.userName}-${user.code}" 
}
mUserNameLiveData.observe(this) {
    Log.e("Test", "mUserNameLiveData更新了:${it.javaClass.simpleName} --- $it") 
}

// 点击更新源LiveData数据
findViewById<Button>(R.id.bt_test).setOnClickListener {
    mUserLiveData.value = User("杰哥", (1..100).random())
}

运行后点击多次更新源LiveData数据,输出结果如下:

6d97b244cbb5c4098d66deedf8447b1b.png

结论:每次源LiveData数据发生变化,转换后的LiveData数据也跟着变化。


Transformations.switchMap()

同样先看下switchMap()的具体实现:

10f41920a1e7fb166fcec89f6a89c1cb.png

留意传入的函数类型,它的返回值类型是LiveData类型!!!然后在回调执行传入函数后,替换了源LiveData。怎么说?改下上面的简单例子:

val mUserNameLiveData = Transformations.switchMap(mUserLiveData) { user ->
    MutableLiveData<String>().apply { "${user.userName}-${user.code}"  }
}

运行后点击多次更新源LiveData数据,输出结果如下:

b0979613a7fcf8a25d05fffc6e980ec2.png

结论:仅把源LiveData作为触发器,执行传入函数后返回新的LiveData,源LiveData数据发生变化不影响转换后的LiveData数据。

到此,map()和switchMap()的区别就一清二楚了,还有一点要注意,转换方法都发生在主线程。意味着如果在此进行耗时、太复杂的转换操作可能会 堵塞主线程,可以想办法将数据转换操作异步化。如通过CoroutineLiveData,用它得加下依赖:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'

包里有个Transformations.kt,对原方法封装,定义成LiveData的扩展方法,便于链式调用:

ce9ea33cd6253bd79e95e13204bccc72.png

CoroutineLiveData中定义了liveData()这个顶层方法,用于构建CoroutineLiveData。

5d4038a7aafa7e35095bc3d05972b16e.png

具体用法示例如下:

val mUserNameLiveData = mUserLiveData.switchMap() { user ->
    liveData(Dispatchers.Default){
        // 模拟耗时操作
        delay(5000)
        emit( "${user.userName}-${user.code}"  )
    }
}

合并多个LiveData源

场景:页面有多个数据源,单独用LiveData的话,每个都要定义一个observer,很繁琐。

解法:可以利用MediatorLiveData来合并多个数据源,只需定义一个observer。

写个简单示例。

006d90803ce08f80dc096c8ee7951603.png

运行后点击多次按钮,输出结果如下:

faea770fc690d101d63c4d50e71725da.png

细心的你有没有发现mMediatorLiveData发生数据更新这个日志没有输出?

因为它本身也是LiveData,有它自己的value,你得去修改它的值,才会触发数据更新回调~

合并数据源,非常适合存在多个关联网络请求或数据库查询的场景。

到此,关于LiveData的基本使用已经差不多了,和其它Jetpack组件的配合(ViewModel、Room等) 后续学到再讲。

/   常见问题及解决   /

解决LiveData带来的粘性问题

上面说过粘性问题 → 添加新观察者,收到之前分发的值,原因如下:

添加观察者,引起生命周期组件状态变化,进行值分发,新观察者中值的版本号为-1,所以会触发值更新。

LiveData没有像EventBus一样,区分粘性和非粘性事件,也没有提供开关,在需要非粘性的场景,就得我们自己想办法了。

e7565a0a43e5900588c77a8d091eb5c2.png

网上罗列的常见解决方式有三大类,一一讲解下~

方案一:反射干涉Version (无效)

思路如下:

在observe()时,反射拿到LiveData的mVersion,然后赋值给LifecycleBoundObserver的mLastVersion。

具体实现:

class NoLiveData<T>(data: T) : MutableLiveData<T>(data) {
    // 传入粘性标记,区分粘性和非粘性
    fun observe(owner: LifecycleOwner, observer: Observer<in T>, isSticky: Boolean) {
        super.observe(owner, observer)
        if (!isSticky) hook(observer)
    }

    private fun hook(observer: Observer<in T>) {
        // 获取mVersion
        val mVersion = javaClass.superclass.superclass.getDeclaredField("mVersion")
            .apply { isAccessible = true }
        val mVersionValue = mVersion.get(this)

        // 获取mObservers
        val mObservers = javaClass.superclass.superclass.getDeclaredField("mObservers")
            .apply { isAccessible = true }
        val mObserversValue = mObservers.get(this)

        // 获取mObservers对象所属的SafeIterableMap
        val methodGet = mObserversValue.javaClass.getDeclaredMethod("get", Any::class.java)
            .apply { isAccessible = true }

        // 获取LifecycleBoundObserver
        val objectWrapperEntry = methodGet.invoke(mObserversValue, observer)
        val objectWrapper = (objectWrapperEntry as Map.Entry<*, *>).value

        // 获取ObserverWrapper
        val mLastVersion = objectWrapper!!.javaClass.superclass.getDeclaredField("mLastVersion")
            .apply { isAccessible = true; }
        val mLastVersionValue = mLastVersion.get(objectWrapper)

        // 将 mVersion的值 赋值给 mLastVersion
        mLastVersion.set(objectWrapper, mVersionValue)
    }
}

看似可以,但实际运行还是触发了粘性,断点发现activeStateChanged()会优于hook()方法的执行,方案一GG。

方案二:引入中间层

就是定义一个类包裹原始类型,在其中定义一个消费标记,用于标识值变化是否被处理,代码示例如下:

open class OneShotValue<out T>(private val value: T) {
    // 值是否被消费
    private var handled = false
    // 获取值,如果值未被处理则返回,否则返回空
    fun getValue(): T? {
        return if (handled) {
            null
        } else {
            handled = true
            value
        }
    }
    // 获取上次被处理的值,留给手动获取用
    fun peekValue(): T = value
}

// 调用处:
private val nNum = MutableLiveData<OneShotValue<Int>>()
nNum.observe(this) { Log.e("Test", "第一个观察者:${nNum.value!!.getValue()}") }
nNum.value = OneShotValue(100)
nNum.observe(this) { Log.e("Test", "第二个观察者:${nNum.value!!.getValue()}") }

运行结果如下:

92b5f6c6e3db88519b60bc286073f44d.png

可以看出依旧是粘性的,只是第二次拿到的值为null,执行回调代码时,记得做下判空,为空不执行具体业务逻辑。

方案三:拦截观察者回调

谷歌官方给出的一个解决方案,源码可见:SingleLiveEvent.java。

class SingleLiveEvent<T> : MutableLiveData<T>() {
    // 标志位,用于表达值是否被消费
    private val mPending: AtomicBoolean = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
        // 中间观察者
        super.observe(owner) { t ->
            // 只有当值未被消费过时,才通知下游观察者
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            } else {
                Log.e("Test", "其实还是粘性...")
            }
        }
    }

    @MainThread
    override fun setValue(@Nullable t: T?) {
        // 当值更新时,置标志位为 true
        mPending.set(true)
        super.setValue(t)
    }

    @MainThread
    fun call() {
        value = null
    }
}

添加了一个中间观察者,接管传入观察者回调执行的执行 ,添加一个mPending标记位(值会在setValue时更新),如果消费过就不执行回调。

这种玩法可以,但存在一个问题:所有观察者共享一个mPending 会导致第一个观察者消费后,其他观察者没机会消费。举个例:依次添加了A、B、C三个观察者,当值发生改变时,三个观察者都应该执行回调,但是用了这个类,实际上只有A会执行回调。

要解决上面这个问题也不难啊,给Observer包一层,添加一个当前观察者是否消费了数据的标记,具体代码如下:

class SingleLiveEvent<T> : MutableLiveData<T>() {
    // 暂存中间观察者
    private val observers = ArraySet<ObserverWrapper<in T>>()

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
        // 避免重复添加
        observers.find { it.observer === observer }?.let { _ -> return}
        ObserverWrapper(observer).apply {
            observers.add(this)
            super.observe(owner, this)
        }
    }

    @MainThread
    override fun observeForever(observer: Observer<in T>) {
        // 避免重复添加
        observers.find { it.observer === observer }?.let { _ -> return}
        ObserverWrapper(observer).apply {
            observers.add(this)
            super.observeForever(this)
        }
    }

    @MainThread
    override fun removeObserver(observer: Observer<in T>) {
        if (observer is ObserverWrapper && observers.remove(observer)) {
            super.removeObserver(observer)
            return
        }
        val iterator = observers.iterator()
        while (iterator.hasNext()) {
            val wrapper = iterator.next()
            if (wrapper.observer == observer) {
                iterator.remove()
                super.removeObserver(wrapper)
                break
            }
        }
    }

    @MainThread
    override fun setValue(@Nullable t: T?) {
        // 遍历更新所有中间观察者的标记为
        observers.forEach { it.newValue() }
        super.setValue(t)
    }

    // 中间观察者
    private class ObserverWrapper<T>(val observer: Observer<T>) : Observer<T> {
        // 标记当前观察者是否消费了数据
        private var pending = false

        override fun onChanged(t: T?) {
            // 保证只向下游观察者分发一次数据
            if (pending) {
                pending = false
                observer.onChanged(t)
            } else {
                Log.e("Test", "其实还是粘性...")
            }
        }

        fun newValue() {
            pending = true
        }
    }
}

运行下看看效果:

6604b90fa872d8bc9b7ca7e7599d656d.png

哈哈,其实还是粘性的,类似的方案还有KunMinX大佬开源的:UnPeek-LiveData,以下是某个比较老版本的代码:

public class ProtectedUnPeekLiveData<T> extends LiveData<T> {

    protected boolean isAllowNullValue;

    private final HashMap<Integer, Boolean> observers = new HashMap<>();

    public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer<? super T> observer) {
        LifecycleOwner owner = activity;
        Integer storeId = System.identityHashCode(observer);
        observe(storeId, owner, observer);
    }

    private void observe(@NonNull Integer storeId,
                         @NonNull LifecycleOwner owner,
                         @NonNull Observer<? super T> observer) {

        if (observers.get(storeId) == null) {
            observers.put(storeId, true);
        }

        super.observe(owner, t -> {
            if (!observers.get(storeId)) {
                observers.put(storeId, true);
                if (t != null || isAllowNullValue) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @Override
    protected void setValue(T value) {
        if (value != null || isAllowNullValue) {
            for (Map.Entry<Integer, Boolean> entry : observers.entrySet()) {
                entry.setValue(false);
            }
            super.setValue(value);
        }
    }

    protected void clear() {
        super.setValue(null);
    }
}

不难看出另外定义了一个 HashMap<observer的唯一id,是否消费过的标记>,原理大同小异。

方案四:不用LiveData,改用Kotlin-Flow

今年的谷歌I/O大会,Yigit在Jetpack的AMA中明确指出Livedata的存在就是为了照顾Java的使用者,短期内会继续维护。作为Livedata的替代品Flow会在今后渐渐成为主流,用上Flow,就不存在粘性问题了。

9a28150d25c3fef372180130bc7d4c9d.png

Kotlin Flow笔者还没来得及学,后续学完再来填这里的坑哈~

829f7f01c535ac7528afde8268b6841b.png


LiveData会丢失数据吗?

答:在高频数据更新的场景下使用 LiveData.postValue() 时,会造成数据丢失。因为设值和分发值是分开执行的,存在延迟。值先被缓存在变量中,再向主线程抛一个分发值的任务。在这个延迟期间,再调用一次postValue(),变量中缓存的值被更新了,会导致之前的值在未分发前就被擦除。

相关代码如下:

d9f5a95ab7832c9a46187930cf7b0102.png


lambda优化隐藏的坑

具体探究过程可以看下:《奇怪的编译优化》,直接说结论:

lambda写法,编译器在编译时会自作聪明优化成添加的同一个静态的观察者。

private void test3() {
    for (int i = 0;i < 10; i++) {
        model.getCurrentName().observe(this, s -> Log.v("ttt", "s:" + s))
    }
}

上述代码值发生改变,并不会收到10条通知,只会收到1条,引入外部变量可以绕过这个优化。

Fragment中使用LiveData的注意事项

Fragment和其中的View生命周期不完全一致,观察LiveData时用viewLifecycleOwner而不是直接用this。

LiveData的数据抖动问题

所谓的数据抖动,LiveData的setValue()不会判断值是否与旧值相等,都会回调Observer.onChange()。

可以通过扩展Transformations.kt中的distinctUntilChanged()方法来解决,代码示例如下:

 
 
private val nNum = MediatorLiveData <Int>()
private val nMediatorData = Transformations.distinctUntilChanged(nNum)
nMediatorData.observe(this) {Log.e("Test", "观察者:${nNum.value!!}") }
findViewById<Button>(R.id.bt_test).setOnClickListener { nNum.value = 100 }

此时疯狂点击,控制台也只有一行输出,看一波源码~

7166229ced412a7a92496bd35cd68d91.png

啧啧,又是标志位,第一次肯定进,非第一次判断新旧值是否相等,非常简单~

/   总结   /

本节过了下LiveData用法,还对常见问题进行了归纳,虽说没有系统过一遍源码,不过大概怎么实现的心里也算有个底,使用起来也有的放矢了。有问题或者建议欢迎在评论区提出,肝文不易,如果本文有帮到你的话,可以给个三连,谢谢~

ab30c55b2b9fff71470d16c90ac62ee0.png

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

关于 Android 渲染你应该了解的知识点

DataBinding → 数据绑定 (使用篇)

欢迎关注我的公众号

学习技术或投稿

3074680d817e641d785434d99728202a.png

e9815b97dc774586873656e5bf6070f2.png

长按上图,识别图中二维码即可关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值