Jetpack 系列文章都是在学习时候的笔记
写文章的思路是从想到什么问题,到解释这个问题来进行表述的.
不建议通篇阅读, 关心那个看对应的实现即可.
需要搞清楚的问题:
1: LiveData 存在的意义是什么?
LiveData/MutableLive/MediatorLiveData/CoroutineLive 功能构建了一个可以被 View 观察的变化的数据源.
更方便的做到了LiveData 的生命周期和 View 的生命周期的绑定和协程提的生命周期的绑定.
更安全的更方便的更新数据, 触发 View 的刷新.
2: 如何实现数据的监听和更新?
总述和需要关心的核心问题
总述
简述: 在 UI 位置把一个监听数据变化的 Observe 的对象塞进 LiveData 的监听器列表中, 当数据发生变化的时候会遍历这个监听器列表, 通知到 UI 中的匿名内部类从而实现页面的更新.
可以轻易导致的问题.
(1): 拿了界面的匿名内部类, 到了 LiveData 中, 如果 LiveData 一直不销毁, 那页面就会泄露.
(2): 是回调的方式, 那触发回调的位置的线程如果不保证, 就会出现在非 UI 线程更新 UI.
监听和更新
监听 → 两种方式
(1): 生命周期绑定 observe(LifecycleOwner, Observe),
(2): 和生命不周期绑定 observeForever(observe)
更新 → 两种方式
(1): 非主线程 poseValue(value)
(2): 主线程 setValue(value)
流程详解
(1) observe(LifecycleOwner, Observe) 生命周期管理的监听做了什么? 如何管理生命周期?是否能解决匿名内部类的泄露问题?
通过把传入的 observe 和 LifeCycleOwner 在 LifeCycleBoundObserver 中进行绑定. 在 LifeCycleBoundObserver 监听 Owner 的状态, 在 Owner onDestroyed的时候进行解绑.从而避免View 的匿名内部类的泄露.
(2) observeForever(observe) Forever 存在的目的是什么? Forever 是否存在泄露的问题?
构造了一个 AlwaysActiveObserver 之后, 直接把他塞入了 observes 中, 比没有处理泄露的问题.
这里需要注意的是, observes 中存的是 ObserverWrapper, 是 AlwaysActiveObserve 和 LifeCycleBoundObserve 的父类. 生命周期的管理交给了 具体的 Observe 实现. 这样也就实现了抽象和使用.
(3) setValue(value) 更新数据的流程
→ 修改版本 Version, 这里不需要做线程管理,以为 setValue 限制了在主线程执行.
→ 修改具体的值 → 分发变化.
(4) poseValue(value) 异步更新在哪里切换的线程?
异步更新关键点: (a): 通过主线程的 Handle → post 了一个 Runnable, 来实现数据的更新
(b): 如果在这里post 没有消费之前有其他数据到, 就把这段时间的数据进行合并. 实现这个关键点: → 让生成和消费写入标志的过程变得原子. → 也就是加锁. 如果在没有消费前,其他生产者产生数据, 只更新数据, 不产生消费的过程.
3: 如何和 LifeCycleOwner 进行的有效绑定?
在监听数据变化的 observe 接口中需要传入一个 LifeCycleOwner, 并将 Observer 和 LifecycleOwner 绑定在LifecycleBoundObserves 中. 并在其中监听 LifeCycleOwner 的生命周期的变化, 当监听到 OnDestroy 的时候 remove 调这个 Observer. 这样实现了和生命周期的绑定和取消绑定.
4: 存在哪些局限, 又出现了哪些解决方案?
(1): KMP 的局限, LiveData 适合 only Android. LiveData 依赖(lifecycle, handler)
(2): 异步更新数据的时候可能存在丢数据. 这也不能算是问题, 因为对数据可以合并刷新 UI, 这样更 UI 刷新更友好. (所谓的丢数据详细描述: 多协程 postValue 的时候, 数据的修改未被消费的时候, 就不会在通过 handler 派发消费任务,只更新值.当消费的时候就会是最新的值, 所以这里可能丢失状态. 这里需要补 Flow 为什么不会 → StateFlow 也会. ShareFlow不会.)
5: 和 StateFlow 有什么异同, 为什么现在会推荐使用 StateFlow. (整理完 StateFlow 这里需要再补充.)
(1): KMP 时代,新项目都推荐使用 StateFlow.
(2): StateFlow 更加灵活, 可以搭配 repeatOnLifecycle 来实现界面不可见的时候取消监听逻辑
(3): StateFlow 有默认值更安全.
6: MediatorLiveDate 用法, 实现.
两种用法:
(1): 合并多个 LiveData.
val combineLive = MideatorLiveData<Int>()
combineLive.addSource(liveData1){ value ->
combineLive.value = value
}
combineLive.addSource(liveData2){ value ->
combineLive.value = value
}
(2): 控制 LiveData 的回调.
val controlLive = MideatorLiveData<Int>()
var count = 0
controlLive.addSource(liveData1){ value ->
count ++
combineLive.value = value
if(count>10){
controlLive.removeSource(Livedata1)
}
}
实现解析:
addSource(LiveData, observer)
给传入的 LiveData 添加了一个 Forever 的 Observe, 在回调中调用传入的 Observe.
removeSource(LiveData)
移除 LiveData 绑定的 Forever 的 Observe.
局限性:
只能合并类型一样的 LiveData
7: MediatorLiveData 存在的价值是是什么?
价值也就是他的用法,
(1): 把两个同数据类型的 LiveData 的变化归拢到一个 LiveData 中.
(2): 对 LiveData 的回调进行控制, 就不用把逻辑放在 View 中了.
8: CoroutineLiveData 用法, 实现.
用法:
val coroutineLive : LiveData<Int> = livedata{
delay(3000)
emit(3)
}
val user: LiveData<String> = userId.switchMap { id ->
liveData {
while(true) {
val data = fakeTransformData(id)
emit(data)
delay(30_000)
}
}
}
val user = liveData {
val fromDb: LiveData<User> = roomDatabase.loadUser(id)
emitSource(fromDb)
val updated = api.fetch(id)
roomDatabase.insert(updated)
}
核心:
(1) liveData{} 构建函数做了什么?
// (1): 创建了一个LiveData, 具体的实现是 CoroutineLiveData.
// (2): block 是 LiveDataScope 的子函数, 并且是 suspend, 所以可以调用 emit emitSource 函数的能力.
fun<T> liveData(
context: coroutineContext = EmptyCoroutineContext, // 协程的 Context
timeoutInMs: 5000L, // 这个是做什么的?
block: suspend LiveDataScope<T>.() -> Unit // 类型是 LiveDataScope的扩展函数的block
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
interface LiveDataScope<T>{
suspend fun emit(value: T)
suspend fun emitSource(source: LiveData<T>): DisposableHandle
val lastestValue:T?
}
(2) 为什么是 CoroutineLiveData
- 创建一个和
LiveData生命周期一直的主线程协程来执行block{}. - 把
Block的逻辑直接放在协程中, 这样就不用再创建协程. - 协程的执行和取消和
LiveData的active和inActive进行绑定, 也就是间接的实现了内部协程和ViewLifeCycle的绑定. // 当然也可以自己来和view生命周期一直的协程来做这个事.
CoroutineLiveData 内部持有一个专门用来执行协程任务的 BlockRunner.
BlockRunner 只做两件事, run()和 cancel(), 两者又分别和 LiveData 的生命周期绑定. run()就是使用传入 CoroutineContext 来 (1): 创建主线程的协程, (2)创建 LiveDataScopeImpl 的具体实现, (3): 在协程中把 Scope 的具体实现把 Block run 起来.
cancel() 把 run()创建的协程取消.
(2) emit() 方法做了什么?
emit → LiveDataScopeImpl.emit → 更新 value.
(3) emitSource() 方法做了什么?
给 LiveData 添加一个数据源. 数据源更新, LiveData 更新.
(4) switchMap{} 函数是做什么的?
LiveData<X>.switchMap(
transform: (X) -> (LiveData<Y>)?
): LiveData<Y>
产生一个新的数据源. 当 LiveData变化,就会产生一个数据源. 特别使用的场景就是, 根据 uid, 观察这个 uid 对应的数据变化. 这样这个 uid 发生的长时间的数据变化都可以被察觉到.
public fun <X, Y> LiveData<X>.switchMap(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
): LiveData<Y> {
var liveData: LiveData<Y>? = null
val result = // 拿到初始值,创建 MediatorLiveData.
if (isInitialized) {
val initialLiveData = transform(value as X)
if (initialLiveData != null && initialLiveData.isInitialized) {
MediatorLiveData<Y>(initialLiveData.value)
} else {
MediatorLiveData<Y>()
}
} else {
MediatorLiveData<Y>()
}
result.addSource(this) { value: X -> // 持续观察 LiveData<X>
val newLiveData = transform(value) // 拿到新的数据源
if (liveData !== newLiveData) {
if (liveData != null) {
result.removeSource(liveData!!) // 移除老的数据源
}
liveData = newLiveData
if (liveData != null) {
result.addSource(liveData!!) { y -> result.setValue(y) } // 重新绑定新的数据源
}
}
}
return result
}
9: CoroutineLiveData 存在的价值是什么?
通过 liveData{} 构造函数可以创建一个 CoroutineLiveData. CoroutineLiveData 包含了一个协程, 这个协程的生命周期和 LiveData 的生命周期已经使用 LiveData 的View 的生命周期进行了绑定. 就不需要在 viewModel 中再去找协程 Scope 来执行协程.
(1): 创建了一个生命周期可控的协程, 不同自己再去找.
(2): 更方便的执行 Suspend 方法.
(3): 可以通过 emitSource()来添加数据源.
10: LiveData 和 MutableLiveData 有什么区别?
LiveData 的 setValue 和 postValue 都是protected 的, 所以外部不能写,只能读.
MutableLiveData setValue 和 postValue 是可以写的.
11: 存在哪些好的设计模式?
(1): 区分 post 和 set.
(2): post 异步优化策略.
- 添加一个状态标志表示是否消费.
- 对标志的读写添加锁, 保持原子性.
- 在消费标志没有完成的情况下, 只改值,不发生新的消费行为.
- 这样就可以可并多线程的修改, 一次消费, 消费多个生产.