文章目录
LiveData
ViewModel的主要作用是存放页面所需要的各种数据。我们在示例代码中定义了接口,当数据发生变化时,采用接口的方式实现对页面的通知。对此前面已经做了相关说明,通过接口的方式对页面进行通知是可行的,但如果要观察的数据很多,则需要定义大量的接口,代码会显得十分冗余。为此,Jetpack提供了LiveData组件。
LiveData是一个可被观察的数据容器类。具体说来,可以将LiveData理解为++一个数据的容器,它将数据包装起来,使数据成为被观察者,当该数据发生变化时,观察者能够获得通知++。我们不需要自己去实现观察者模式,LiveData内部已经默认实现好了,我们只要使用就可以了。
LiveData取代了上一章中所定义的接口,帮助我们完成ViewModel与页面之间的通信。
LiveData是一个可观察的数据持有者类。与常规的可观察对象不同,LiveData具有生命周期意识,这意味着它尊重其他应用程序组件的生命周期,如活动、片段或服务。这种意识确保LiveData只更新处于活动生命周期状态的应用程序组件观察员。//翻译于官方文档
LiveData
是一种持有可被观察数据的类。和其他可被观察的类不同的是,LiveData
是有生命周期感知能力的,这意味着它可以在activities
,fragments
,或者services
生命周期是活跃状态时更新这些组件。那么什么是活跃状态呢?ViewModel中的**STARTED
和RESUMED
就是活跃状态,只有在这两个状态下LiveData
是会通知数据变化的。**
要想使用LiveData
(或者这种有可被观察数据能力的类)就必须配合实现了LifecycleOwner
的对象使用。在这种情况下,当对应的生命周期对象DESTROYED
时,才能移除观察者。这对Activity
或者Fragment
来说显得尤为重要,因为他们可以在生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄漏等问题。
使用LiveData
的优点:
UI
和实时数据保持一致 因为LiveData
采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI
。- 避免内存泄漏,观察者被绑定到组件的生命周期上,当被绑定的组件销毁(
destory
)时,观察者会立刻自动清理自身的数据。 - 不会再产生由于
Activity
处于stop
状态而引起的崩溃 例如:当Activity
处于后台状态时,是不会收到LiveData
的任何事件的。 - 不需要再解决生命周期带来的问题
LiveData
可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。 - 实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据
- 解决
Configuration Change
问题,在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。 - 数据共享,如果对应的
LiveData
是单例的话,就能在app
的组件间分享数据。
LiveData与ViewModel
ViewModel用于存放页面所需要的各种数据,不仅如此,我们还可以在其中放一些与数据相关的业务逻辑。例如,我们可以在ViewModel中进行数据的加工、获取等操作。因此,ViewModel中的数据可能会随着业务的变化而变化。对页面来说,它并不关心ViewModel中的业务逻辑,它只关心需要展示的数据是什么,并且希望在数据发生变化时,能及时得到通知并做出更新。LiveData的作用就是,在ViewModel中的数据发生变化时通知页面。因此,LiveData通常被放在ViewModel中
使用LiveData
:
- 创建一个持有某种数据类型的
LiveData
(通常是在ViewModel
中) - 创建一个定义了
onChange()
方法的观察者。这个方法是控制LiveData
中数据发生变化时,采取什么措施 (比如更新界面)。通常是在UI Controller
(Activity/Fragment
)中创建这个观察者。 - 通过LiveData实例的
observe()
方法连接观察者和LiveData
。observe()
方法需要携带一个LifecycleOwner
类。这样就可以让观察者订阅LiveData
中的数据,实现实时更新。
创建LiveData对象
LiveData是一个抽象类,不能直接使用。通常我们使用的是它的直接子类MutableLiveData。
LiveData
是一个数据的包装。具体的包装对象可以是任何数据,包括集合(比如List
)。LiveData
通常在ViewModel
中创建,然后通过getter
方法获取。
kotlin
class TimerViewModel : ViewModel() {
private val timer: Timer = Timer()
private var currentSecond: MutableLiveData<Int> = MutableLiveData() //创建LiveData对象
fun start() {
timer.schedule(timerTask {
var value = currentSecond.value
val plus = value?.plus(1)
currentSecond.postValue(plus)
}, 1000, 1000)
}
fun getCurrentSecond() : LiveData<Int> {
return currentSecond
}
override fun onCleared() {
super.onCleared()
timer.cancel()
}
}
观察LiveData中的数据
通常情况下都是在组件的onCreate()
方法中开始观察数据,原因有以下两点:
- 系统会多次调用
onResume()
方法 - 确保
Activity/Fragment
在处于活跃状态时立刻可以展示数据。
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tv = findViewById<TextView>(R.id.tv)
val model = ViewModelProvider(this).get(TimerViewModel::class.java)
// 得到ViewModel
val liveData = model.getCurrentSecond() as MutableLiveData<Int>
//获取LiveData,并调用LiveData实例的observe()方法
liveData.observe(this, object : Observer<Int> {
override fun onChanged(t: Int?) {
tv.text = t.toString()
}
})
// 重置
liveData.postValue(0)
model.start()
}
}
更新LiveData对象
如果想要在UI Controller
中改变LiveData
中的值呢?(比如点击某个Button
把性别从男设置成女)。LiveData
并没有提供这样的功能,但是Architecture Component
提供了MutableLiveData
这样一个类,可以通过setValue(T)
和postValue(T)
方法来修改存储在LiveData
中的数据。MutableLiveData
是LiveData
的一个子类,从名称上也能看出这个类的作用。举个直观点的例子:
java
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
mModel.getCurrentName().setValue(anotherName);
}
})
调用setValue()
方法就可以把LiveData
中的值改为John Doe
。同样通过这种方法修改LiveData
中的值同样会触发所有对这个数据感兴趣的类。那么setValue()
和postValue()
有什么不同呢?区别就是setValue()
只能在主线程中调用,而postValue()
可以在子线程中调用。
observeForever()
LiveData还提供了一个名为observeForever()
的方法,使用起来与observe()
没有太大差别。区别在于,当LiveData所包装的数据发生变化时,无论页面处于什么状态,observeForever()
都能收到通知。(正所谓永远观察)因此,在用完之后,一定要记得调用removeObserver()
方法来停止对LiveData的观察,否则LiveData会一直处于激活状态,Activity则永远不会被系统自动回收,这就造成了内存泄漏。
源码
还有两个重要的方法,分别是observe()
方法和setValue()
方法:
java
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
// 将Observer与Activity的生命周期关联在一起。因此,LiveData能够感知页面的生命周期。它可以检测页面当前的状态是否为激活状态,或者页面是否被销毁。只有在页面处于激活状态(Lifecycle.State.ON_STARTED或Lifecycle.State.ON_RESUME)时,页面才能收到来自LiveData的通知,若页面被销毁(Lifecycle.State.ON_DESTROY),那么LiveData会自动清除与页面的关联,从而避免可能引发的内存泄漏问题。
owner.getLifecycle().addObserver(wrapper);
}
java
/**
* Sets the value. If there are active observers, the value will be dispatched to them.
* 设置值。如果存在活动的观察者,则值将被分派给他们。
* <p>
* This method must be called from the main thread. If you need set a value from a background
* 必须从主线程调用此方法。如果需要从后台线程设置值
* thread, you can use {@link #postValue(Object)}
*
* @param value The new value
*/
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
// 遍历mObservers,调用considerNotify()更新数据
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
java
// considerNotify()方法:
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
//我们仍然首先检查observer.active,将其作为活动的入口。
//因此,即使观察者移动到活动状态,如果我们没有收到该事件,我们最好不要通知更可预测的通知顺序。
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//回调onChanged()方法
observer.mObserver.onChanged((T) mData);
}
Room和LiveData配合使用
Room
可以返回LiveData
的数据类型。这样对数据库中的任何改动都会被传递出去。这样修改完数据库就能获取最新的数据,减少了主动获取数据的代码。
//我还没学完就不展示了
继承LiveData扩展功能
LiveData
的活跃状态包括:STARTED
或者RESUMED
两种状态。那么如何在活跃状态下把数据传递出去呢?下面是示例代码:
kotlin
class 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)
}
}
上面有三个重要的方法:
onActive()
: 当LiveData对象具有活动观察者时调用该方法onInactive()
: 当LiveData对象没有任何活动观察者时调用该方法setValue(T)
: 更新LiveData实例的值,并通知任何活动的观察者有关更改的信息
可以像下面这样使用StockLiveData
:
kotlin
public class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val myPriceListener: LiveData<BigDecimal> = ...
myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
// Update the UI.
})
}
}
上面observe()
方法中的第一个参数传递的是fragment
的实例,该fragment
实现了LifecycleOwner
接口。这样做是为了将observer
和Lifecycle
对象绑定到一起,这意味着:
- 如果当前的
Lifecycle
对象不是处于活跃期,就算value
值有改变也不会回调到observer
中 - 在
Lifecycle
对象销毁后,observer
对象也会自动移除,防止内存泄漏
实际上LiveData
对象是适应生命周期也就意味着你需要在多个activities
,fragments
和services
中进行共享,所以通常我们会将LiveData
的示例设计成单例的:
kotlin
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager: StockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
companion object {
private lateinit var sInstance: StockLiveData
@MainThread
fun get(symbol: String): StockLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
return sInstance
}
}
这样就可以在fragment
中像如下这样使用:
kotlin
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
// Update the UI.
})
}
}
转换LiveData
有时在LiveData
分发给observers
之前想要修改一下存储在LiveData
中的值,或者你想根据当前的值进行修改返回另一个值。Lifecycle
提供了Transformations
类来通过里面的helper
方法解决这种问题。
Transformations.map()
可以将LiveData
中的数据进行改变:
java
val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
user -> "${user.name} ${user.lastName}"
}
将LiveData
中的User
数据转换成String
Transformations.switchMap()
java
private fun getUser(id: String): LiveData<User> {
...
}
val userId: LiveData<String> = ...
val user = Transformations.switchMap(userId) { id -> getUser(id) }
和上面的map()
方法很像。区别在于传递给switchMap()
的函数必须返回LiveData
对象。
和LiveData
一样,Transformation
也可以在观察者的整个生命周期中存在。只有在观察者处于观察LiveData
状态时,Transformation
才会运算 。Transformation
是延迟运算的(calculated lazily
),而生命周期感知的能力确保不会因为延迟发生任何问题。
如果在ViewModel
对象的内部需要一个Lifecycle
对象,那么使用Transformation
是一个不错的方法。举个例子:假如有个UI
组件接受输入的地址,返回对应的邮政编码。那么可以 实现一个ViewModel
和这个组件绑定:
java
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private fun getPostalCode(address: String): LiveData<String> {
// DON'T DO THIS
return repository.getPostCode(address)
}
}
有个// DON'T DO THIS
(不要这么干),这是为什么?有一种情况是如果UI组件被回收后又被重新创建,那么又会触发一次repository.getPostCode(address)
,而不是重用上次已经获取到的查询。那么应该怎样避免这个问题呢?看一下下面的代码:
java
class 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
}
}
postalCode
变量的修饰符是public
和final
,因为这个变量的是不会改变的。那输入不同的地址还总返回相同邮编?当然不是,postalCode
这个变量存在的作用是把输入的addressInput
转换成邮编,那么只有在输入变化时才会调用repository.getPostCode()
方法。这就好比你用final
来修饰一个数组,虽然这个变量不能再指向其他数组,但是数组里面的内容是可以被修改的。绕来绕去就一点:当输入是相同的情况下,用了switchMap()
可以减少没有必要的请求。并且同样,只有在观察者处于活跃状态时才会运算并将结果通知观察者。
合并多个LiveData中的数据
MediatorLiveData
是LiveData
的子类,可以通过MediatorLiveData
合并多个LiveData
来源的数据。同样任意一个来源的LiveData
数据发生变化,MediatorLiveData
都会通知观察他的对象。说的有点抽象,举个栗子。比如UI
接收来自本地数据库和网络数据,并更新相应的UI
。可以把下面两个LiveData
加入到MeidatorLiveData
中:
-
关联数据库的
LiveData
-
关联联网请求的
LiveData
相应的
UI
只需要关注MediatorLiveData
就可以在任意数据来源更新时收到通知。