Jetpack--LiveData
LiveData 速描
资料来源:
https://developer.android.com/topic/libraries/architecture/livedata
更新
1
20.02.24 初始化
导语
- 开始刷 Jetpack 其他部分了.初期还是官方文档总结.
- 导入 LiveData 不涉及了.
- 怎么感觉坑反而越来越多了😂
基础
livedata 优势
- 最主要的一条是具有生命周期感知能力,只有绑定的观察者(Activity/Fragment)在活跃状态, livedata 才会更新数据.避免了内存泄漏.
- 观察者被销毁时,livedata 的绑定会自动解除.
- 配置改变(设备旋转等)重新创建 Activity/Fragment livedata 会立刻接收最新数据.
- 使用单例模式拓展 livedata 对象封装系统服务,便于共享.
使用 livedata
- 创建 livedata 的实例,通常在 ViewModel 中.
- 创建可定义 onChanged() 方法的 Observer 对象,该方法可以控制当 LiveData 对象存储的数据更改时会发生什么.通常情况是在在界面控制器 (Activity/Fragment) 中创建.
- 使用 observe() 方法将 Observer 对象订阅 LiveData 对象.这样在 livedata 发生变化且 Observer 对象处于活跃状态时收到通知.
- ps:可以使用 observeForever(Observer) 方法来注册未关联 LifecycleOwner 对象的观察者.这样观察者会被视为始终处于活跃状态,因此它始终会收到关于修改的通知.您可以通过调用 removeObserver(Observer) 方法来移除这些观察者.
创建 livedata 对象
LiveData 是数据封装容器,可以封装任何数据类型.包括 List 等.通常用在 ViewModel.(关于ViewModel 又是另外一篇了)
例:
1
2
3
4
5
6
7class NameViewModel : ViewModel() {
// Create a LiveData with a String
val currentName: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
// Rest of the ViewModel...
}
观察 LiveData
通常应用组件的 onCreate() 方法是开始观察 LiveData 对象的起点:
- Activity/Fragment 的 onResume() 在整个生命周期可能会调用多次,并不适合作为订阅的起点.
- 可以确保 Activity/Fragment 变为活跃状态后可以立即显示的数据.
通常 LiveData 只有在数据更改时才发送更新给活跃的观察者.当观察者从非活跃状态转换为活跃状态时也会收到 LiveData 更新,但是当观察者第二次由非活跃转入活跃且 LiveData 的值并没有发生变化时并不会收到更新.
例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class NameActivity : AppCompatActivity() {
private lateinit var model: NameViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 设置 ViewModel.
model = ViewModelProviders.of(this).get(NameViewModel::class.java)
// 实例化 observer 对象
val nameObserver = Observer<String> { newName ->
// Update the UI, in this case, a TextView.
nameTextView.text = newName
}
// 绑定 LiveData
model.currentName.observe(this, nameObserver)
}
}
更新 LiveData
- LiveData 没有公开可用的方法来更新存储的数据.MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,如果您需要修改存储在 LiveData 对象中的值,则必须使用这些方法.
- 通常在 ViewModel 中使用 MutableLiveData,然后 ViewModel 只会向观察者公开不可变的 LiveData 对象.
- setValue(T) 从主线程更新 LiveData 对象. postValue(T) 可以在其他线程调用来更新 LiveData 对象.
LiveData 与 Room 配合
- 暂且不表.简而言之 Room 支持直接返回一个 LiveData.
LiveData 与 kotlin 协程(非常赞!!)
- 详情见 将协程与 LiveData 一起使用
- liveData 构建器函数调用 suspend 函数,并将结果作为 LiveData 对象传送.
扩展 LiveData
可以自定义类继承自 LiveData ,改变 LiveData 在观察对象活跃非活跃时的一些动作.
- onActive(): LiveData 对象具有活跃观察者时调用 onActive().
- onInactive(): LiveData 对象没有任何活跃观察者时调用 onInactive(),由于没有观察者在监听,需要与 StockManager 服务断开连接.
例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
}使用 StockLiveData 与使用普通 LiveData 完全相同.
LiveData 对象具有生命周期感知能力,这意味着您可以在多个 Activity/Fragment/Service 之间共享同一 LiveData 实例.一个好的方式是实现一个单例 LiveData.(kotlin 中使用伴生对象非常方便)
例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
// do something
...
companion object {
private lateinit var sInstance: StockLiveData
fun get(symbol: String): StockLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
return sInstance
}
}
}
转换/合并 LiveData
个人感觉这个非常类似 rxjava 的流式处理…不知道是不是错觉…
LiveData 对象分派给观察者之前需要对值进行更改,或者您可能需要根据另一个实例的值返回不同的 LiveData 实例.Lifecycle 软件包会提供 Transformations 类.
Transformations.map(): 对每个值应用 lambda,并将结果返回.
1
2
3
4val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
user -> "${user.name} ${user.lastName}"
}Transformations.switchMap(): 与 map() 类似,对存储在 LiveData 对象中的值应用函数,并将结果解封和分派到下游.传递给 switchMap() 的函数必须返回 LiveData 对象.
1
2
3
4
5private fun getUser(id: String): LiveData<User> {
...
}
val userId: LiveData<String> = ...
val user = Transformations.switchMap(userId) { id -> getUser(id) }
可以使用转换方法在观察者的生命周期内传送信息.除非观察者正在观察返回的 LiveData 对象,否则不会计算转换.因为转换是以延迟的方式计算,所以与生命周期相关的行为会隐式传递下去,而不需要额外的显式调用或依赖项.
这段话有点费解.举个例子:
例如这段代码是 ViewModel 中一个地址和邮编的转换.(千万别这么写…)
1
2
3
4
5
6
7class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private fun getPostalCode(address: String): LiveData<String> {
// DON'T DO THIS
return repository.getPostCode(address)
}
}LiveData 对应的观察实例重建后会向实例返回 LiveData 的值,但是向上面的写法,等于每次都再调用 repository.getPostCode(address) 而不是返回之前已经得到的结果,这样就违背了使用 LiveData 的原则.
写成下面这个样子,就算观察实例重建,只要 addressInput 没有发生更改,就不会重复调用 repository.getPostCode(address).观察实例重建只会返回之前的结果.
1
2
3
4
5
6
7
8
9class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private val addressInput = MutableLiveData<String>()
val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
address -> repository.getPostCode(address) }
private fun setInput(address: String) {
addressInput.value = address
}
}
这些转换也可以自定义,需要使用 MediatorLiveData 类.该类可以监听其他 LiveData 对象并处理它们发出的事件,MediatorLiveData 正确地将其状态传播到源 LiveData 对象.要详细了解此模式,请参阅 Transformations 类的参考文档.(占个坑,后面补)
合并多个 LiveData 源,大概会在涉及数据请求,合并网络的信息和本地数据库的信息时用到.(占坑,后面填)