一起养成写作习惯!这是我参与「日新计划 4 月更文挑战」的第2天,点击查看活动详情。
0x1、引言
在开始这篇文章前,我就遇到了第一个关于LiveData的问题:该怎么翻译这个词呢?
- 活的数据?→ 有点奇怪,难不成还有死的数据?
- 可观察数据?→ 感觉跟Observable有点沾边了
- 生变量与函数命周期感知数据?→ 名字也实例化和初始化的区别忒长了…
自己想不到,那就集思广益,尝试向群里的小伙伴发起咨询:
好像也没找到比较好的翻译 (好像越来越可刑),索性就不翻了,哈哈哈~
LiveData 一言以蔽之
LiveData
是 能感知生命周期的
、可观察的
、粘性
的 数据持有者
,以 数据驱动
的方式更新界面闰土刺猹。
① 感知谁的实例化对象生命周期?
具有生命周期感知android是什么手机牌子的组件,一般代指Activity、Fragment,但不局限此,更泛指实现了 Lifecycle.LifecycleOwner 的组件。
如:利用 Lifecycle 让 App进程 拥有生命周android/harmonyos期感知 → ProcessLifecycleOwneandroid是什么手机牌子r,具体实现解读:《自定义 Lifecycandroid下载安装leOwner》
简单点说:实现了Lifecycle那一套的组件,Activit谷歌y、Fragment默认实现了,所以可以直接用,具体原理解读:《【Jetpack】学穿:Lifecycle → 生命周期 (原理篇)》
② 怎么感知生命周期?
-
LifecycleOwner
提供了一个getLifecycle()
用于获取Lifecycle实变量名的命名规则例
; - 你只变量值需自谷歌三件套定义一个变量名的命名规则 观察者 实现
LifecycleEventObserver
或DefaultLifecycleObserver
,谷歌商店调用Lifecyc谷歌le实例的addObserve()
添加观察者实例即可。 - 当组件生命周期发生变化,就会通知所有的观察变量的定义者(回调方法)。
LiveData里,还对观察者包了一层 → Lifecyc谷歌浏览器leBoundObserver
图中圈住的①:当组件处于 DESTORYED 状态时,自动移除观察者,这样的好处:避免内存泄露。
图中圈住的②,点开 activ谷歌翻译eStateChanged()
方法:
当组件处于 STARTED/RESUMED 状态,才发送数据,这样的好处:避android下载安装免因组件处于非活跃状态,接收LiveData事件导致的崩溃,最常见的就是Activity停止了,还执行相关回调。
③ 可观察
除了 Lifecycle谷歌商店BoundObserver 生命周期观察者谷歌商店外,LiveData谷歌空间内部还提供了一个 数据观察者 Observer
可以调用变量值 observe() 添加数据观察者:
在 d肉跳测吉凶ispatchingValue()
分发事件,considerNotify(Android)
中回调 onChanged()
,完成数据更新。
④ 粘性
所谓的 粘性,通俗点说:先发送数据,然后变量之间的关系订阅,也可以收到让天秤倒追的星座之前发的数据。
在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)
}
运行后输出日志如下:
现象:第二个观察者刚注册,就收到分发的值。这是谷歌空间咋回事?
不急不急,先来探一探 值分发的原理,首先,要实现粘性,得把值存起来 吧!跟下构造方法:
不难看出一丝端倪:
- 用一个 Object 类型的 mData 存值;
- 用一个 int 类型的 mV谷歌安装器ersion 来标识值的版本,初始值-1,如果版本号小于最新版本号,表示当前的值要更新。
跟下更新值的方法:
新值覆盖旧值,使值的版本号自增1,值分发 (遍历所有观察者并分发)
观察者中也持有一个值的版本号,ObserverWrapper
包 Obsandroid是什么系统erver 带让天秤倒追的星座的私货:
这样设计的好处:数据始终保持最新状态。变量与函数
值发生变化后遍历所有观察者,看到这里,读者可能有这样的疑问:
生命周期组件处于非活跃状态,如Activity切到后台,此时发生数据变化,然后切回前台,又是怎么更新的?
留意下面的代码:
当生命周期组件发生状态流转,就会回调onStateChanged,Activity从后台到前台自然会走这里,完成数人头攒动据更新。注意:这类的实例化里是 单独分发给和生命周期组件绑定实例化和初始化的区别的观察者,不是群发!!!
总结下:值变更 + 生命周期组件状态变更 都会引起值分发。
到此,好像也没定位到引起粘性的原因? 嗯,还得再跟一下:LiveData.observe()
→ owner.getLifecycle().addOb人头攒动server(wrapper)
→ LifecycleRegistry.addObservandroid什么意思er()
Lifecycle添加观察者,状态流转,调用 dispatchEvent()
方法完成事件分发:
回调了 onStateChanged()
方法,符合第二种场景 → 生命周期组件状态变更:
activeStateChanged()
→ dis实例化对象是什么意思patchin人头攒动gValue()
→ c变量泵onsiderNotify()
:
粘性的原因到此就一清二楚了,变量与函数此处应有掌声!!!
怎么解决粘性问题?不急,等下讲,这里TM才开头而已,LiveData用法都还没介绍呢。
⑤ 数据驱动
简单点说:不需要主动给界面推(设置数让天秤难以放弃的星座据),界面会被动观察数据变化。
0x2、LiveData 使用详解
老规矩,官方文档双手奉上:《LiveData 概览》,以官人体承受的最大电压方文档和源码为准~
① 依赖组件
LiveData基本配着ViewModel一起用的,你实在不想依赖,可以按官方文档的来:Lifecycle
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:
命令实例化servlet类异常行键人头攒动入:gradlew :app:dependencies > dependencies.txt
扫一波就知变量道了:
② 创建、观察、更新
创建就不用说了,LiveData是抽象类,要么继承自定义,要谷歌安装器么用 MutableLiveData
。
观察、更新在粘性那里的例子已经演示得很清楚了,就不Copy了,这里提一嘴 添加android的drawable类观察者的两个方法:
-
observe()
→ 当生命周期组件处于STARTED和REandroid下载SUMED状态下才会收到分发值。 -
observe变量泵Forever()
→ 跟生谷歌play命周期组件状态android平板电脑价格没关系了,都会收到分发值,要手动调removeObserver()
移除!
还有android的drawable类 两种改变数据的方法:
-
setValue()
→ 只能在主线程中调用; -
postValue()
→ 既可以在主线程中调用,也可以在子线程中调用,最终调用的还是setValue();
③ 为什么LiveData搭配ViewModel使用?
- 1、避免Activity、Fragment的代码臃肿,不塞ViewModel里也要做数据和组件分离;
- 2、将LiveData与特定的Ac人头攒动的读音tivity、Fragment分离,配置发生更改(如旋转)导致重建,不影响LiveData。
④ LiveData扩展
就是LiveData预留了两个回调:onActive()
和 onIna谷歌空间ctive()
,当生命周期组件 在活跃和非活跃状态间切换 时会回调。
可以怎么玩?比如写一个全局可用的倒计谷歌play时器,先写倒计时相关功能:
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) 不活跃时不更新页面,活跃时才更新。运行效果如下:
Tips:只是写下例子,真正用到项目还得自己改下哈,如添加/移除观察者时加锁 ~
⑤ LiveData转换
Lifecycle包提供了 Transforma变量之间的关系tions
来对LiveData的数据类型进行转换,可以 在数据返回给观察android的drawable类者前,修改LiveData中数据的具体类型。
Transfor变量值mat变量名的命名规则ions
中有两个常用的转换方法 map()
和 swtichMap()
,它们都是使用 MediatorLiveDa变量名的命名规则ta
作为数据的中间消费者,并将转换后的数据传递给最终消费者。
Tr谷歌playansforRTCmations.map()
先看下map()的具体实现:
流程解读:
- 实例android下载安装化一个
MediatorLiveData
实例 result; - 调用
addSource()
传入源LiveData
和新建的Observer实例
; - 在Observer实例化servlet类异常的回调方法onChange()中,把 转换函数实例化和初始化的区别的执行结android的drawable类果人头攒动 传入re变量的定义sult的setValue()方法中;
- 最后返回result;
跟下:MediatorLiveD变量的定义ata.实例化一个对象可以使用什么命令addSource()
:
用Source包了一层,将传入参数关林,android的drawable类直接跟 Source.plug()
吼,就人头攒动的读音是给 源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())
}
运行后点击多次更新源LiveRTCData数据,输出结果如下:
结论:每次源LiveData数据发生变化,转换后的LiveData谷歌商店数据也跟着变化。
Transformations.switchMap()
同样先看下switchMap()的android是什么手机牌子具体实现:
留意变量名传入的函数类型,它的返回值类型是 LiveData类型变量与函数!人体承受的最大电压!!然后在回调执行传入函数后,替换了源LiveData。怎么变量泵说?改下上面的简单例子:
val mUserNameLiveData = Transformations.switchMap(mUserLiveData) { user ->
MutableLiveData<String>().apply { "${user.userName}-${user.code}" }
}
运行后点击多次更新源LiveData数据,输出结果如下:
结论:仅把源LiveData作为触发器,执行传入函数后返回新的LiveData,源LiveData数据发生变化不影响转换后的LiveData数据。
到此,map()和sandroid下载witchM人头攒动的近义词ap()的区别就一清二楚了,还有一点要注意,转换方法都发生在实例化 主线程。意味变量的定义着如果在此进行耗时、太复杂的转换操作谷歌安装器可能会 堵塞主线程,可以想办法将数据转换操作 异步化。如通过 CoroutineLiveData
,用它得加下依赖:
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
包里有个 T变量类型有哪些rans人头攒动的近义词forma让天秤难以放弃的星座tions.kt,对原方法封装,定义成LiveData的扩展方法,便于链式调用:
Coroutandroidstudio安装教程ineLiveData
中定义了liveData()这个顶层方法,用于构建 Coroutin实例化对象eLiveData:
具体用法RTC示例如下:
val mUserNameLiveData = mUserLiveData.switchMap() { user ->
liveData(Dispatchers.Default){
// 模拟耗时操作
delay(5000)
emit( "${user.userName}-${user.code}" )
}
}
⑥ 合并多个LiveData源
- 场景:页面有多个数据android下载源,单独用LiveData的话,每个都要定义一个observer,很繁琐。
- 解变量泵法:可以利用 MediatorLiveData 来合并多个数据源,只需定义一个observer。
写个简单示例:
运行后点击多次按钮,输出结果如下:
细心的你有没有发现 mMediatorLiveData发生数据更新 这个日志没有输出?
因为它本身也是LiveData,有它自己的value,你得去修改它的值,Android才会触发数据更新回调~
合并数据源,非常适合存在多个 关联rtc是什么意思 网络变量泵请求或数据库查询的场景。
到此,关于LiveData的基本使用已经差不多了,和人体肠道结构示意图其它Jetpack组件的配合(ViewModel、Room等) 后续学到再讲。
0x3、LiveData 常见问题及解决谷歌商店
① 解决LiveDat变量值a带来的粘性问题
上面说过粘性问题 → 添加新观察者,收到之前分发的值,原因如下:
添加观察者,引起生命周类的实例化期组件状态变化,进行值分发,新观察者中值的版本号为-1,所以会触发值更新。
LiveData谷歌三件套没有像EventB人体肠道结构示意图us一样,区分粘性和非粘性事件,也没有提供开关,在需要非粘性的场景,就得我们自己想办法了。
网上罗列的常见解决方式变量是什么意思有三大类,一一讲解下~
方案一:反射干涉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()}") }
运行结果如下:
可以看出依旧是粘性的,只是第二次拿到的值为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三个观察者,android/harmonyos当值发生改变时,三个观察者都应该执行回调,但是用了这个类,实际上只有A会执行回调。
要解决上面这个问题也不难啊,给Observer包一层,添加一个当前观察者是否消费了数android是什么系统据的标记,具体代码如下:
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
}
}
}
运行下看看效果:
哈哈,其实还是粘性的,类变量的定义似的方案还有 KunMinX大佬 开源的:UnPeek-LiveD变量之间的关系ata,以下是某个比较实例化servlet类异常老版本的代码:
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,是否消费过的android是什么手机牌子标记>,原理大同小异。
方案四:不用Liv谷歌地图eData,改用Kotlin-Flow
今年的谷歌I/O大会,Yigit 在Jetpack的AMA中明确指出Livedata的存在就是为了照顾J肉跳测吉凶ava的使用者,短期内会继续维护。作为Livedata的替代品Flow会在今后渐渐成为主流,变量用上Flow,就不存在粘性问题了。
Kotlin Fl变量ow笔者还没来得及学,后续学完再来填这里的坑哈~
② LiveData会丢失数据吗?
答:在高频数据更新的场景下使用 LiveData.po谷歌空间stValuandroid下载安装e() 时,会造谷歌翻译成数据丢失。 因为 设值 和 分发值 是分开执行的,存在延迟。值先被缓存在变量中,再向主线程抛一个分发值的任务。在这个延迟期间变量的定义,再调用一次postValue(),变量中缓存的值被更变量是什么意思新了,会导致之前的值在未分发前就被擦除。
相关代码如下:
③ lambda优化隐藏的坑
具体探究过程可以看下:《奇怪的RTC编译优化》,直接说结论:
lambdaandroid是什么手机牌子写法,编译器在编译时会自作聪android下载明优化成 添加的同一个静态的观察者。
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生命周期不完谷歌安装器全一致,观察L变量类型有哪些iveData时变量泵用 viewLifecycleOwner
而不是直接用 this
。
⑤ LiveData肉跳测吉凶的数据抖动问题
所谓的数据抖动:LiveData的setValue()不会判断值是否与旧值相等,都会实例化和初始化的区别回调Observer.变量onChange()。
可以通过扩展 Trans变量英语formations.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 }
此时疯狂点击,控制台也只有一行输出,看一让天秤难以放弃的星座波源码~
啧啧,又是标志位,第一次肯定进,非第一次判断新旧值是否相等,非常简单~
0x4、小结
本节过了下LiveData用法,还对常见问题进行了归纳,虽说没有系统过一遍源码,不过大概怎么实现的心里也算有个底,使用起来也有的放变量英语矢了。有问题或者建议欢迎在评论区提出,肝文不易,如果本文有帮到你的话,可以给个三连,谢谢~
参考文献
-
官方文档《LiveData 概览》
-
LiveData 面试题库、解答、源码分析
-
Jetpack—LiveData组件的缺陷以及应对策略