Android Jetpack 之 LiveData 源码探究

6 阅读7分钟

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) 生命周期管理的监听做了什么? 如何管理生命周期?是否能解决匿名内部类的泄露问题?

通过把传入的 observeLifeCycleOwnerLifeCycleBoundObserver 中进行绑定. 在 LifeCycleBoundObserver 监听 Owner 的状态, 在 Owner onDestroyed的时候进行解绑.从而避免View 的匿名内部类的泄露.

(2) observeForever(observe) Forever 存在的目的是什么? Forever 是否存在泄露的问题?

构造了一个 AlwaysActiveObserver 之后, 直接把他塞入了 observes 中, 比没有处理泄露的问题.

这里需要注意的是, observes 中存的是 ObserverWrapper, 是 AlwaysActiveObserveLifeCycleBoundObserve 的父类. 生命周期的管理交给了 具体的 Observe 实现. 这样也就实现了抽象和使用.

(3) setValue(value) 更新数据的流程

→ 修改版本 Version, 这里不需要做线程管理,以为 setValue 限制了在主线程执行.

→ 修改具体的值 → 分发变化.

(4) poseValue(value) 异步更新在哪里切换的线程?

异步更新关键点: (a): 通过主线程的 Handle → post 了一个 Runnable, 来实现数据的更新

(b): 如果在这里post 没有消费之前有其他数据到, 就把这段时间的数据进行合并. 实现这个关键点: → 让生成和消费写入标志的过程变得原子. → 也就是加锁. 如果在没有消费前,其他生产者产生数据, 只更新数据, 不产生消费的过程.

3: 如何和 LifeCycleOwner 进行的有效绑定?

在监听数据变化的 observe 接口中需要传入一个 LifeCycleOwner, 并将 ObserverLifecycleOwner 绑定在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

  1. 创建一个和 LiveData 生命周期一直的主线程协程来执行 block{}.
  2. Block 的逻辑直接放在协程中, 这样就不用再创建协程.
  3. 协程的执行和取消和 LiveDataactiveinActive 进行绑定, 也就是间接的实现了内部协程和View LifeCycle 的绑定. // 当然也可以自己来和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 异步优化策略.

  1. 添加一个状态标志表示是否消费.
  2. 对标志的读写添加锁, 保持原子性.
  3. 在消费标志没有完成的情况下, 只改值,不发生新的消费行为.
  4. 这样就可以可并多线程的修改, 一次消费, 消费多个生产.