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
的组件间分享数据。ViewModel用于存放页面所需要的各种数据,不仅如此,我们还可以在其中放一些与数据相关的业务逻辑。例如,我们可以在ViewModel中进行数据的加工、获取等操作。因此,ViewModel中的数据可能会随着业务的变化而变化。对页面来说,它并不关心ViewModel中的业务逻辑,它只关心需要展示的数据是什么,并且希望在数据发生变化时,能及时得到通知并做出更新。LiveData的作用就是,在ViewModel中的数据发生变化时通知页面。因此,LiveData通常被放在ViewModel中
使用LiveData
:
LiveData
(通常是在ViewModel
中)onChange()
方法的观察者。这个方法是控制LiveData
中数据发生变化时,采取什么措施 (比如更新界面)。通常是在UI Controller
(Activity/Fragment
)中创建这个观察者。observe()
方法连接观察者和LiveData
。observe()
方法需要携带一个LifecycleOwner
类。这样就可以让观察者订阅LiveData
中的数据,实现实时更新。LiveData是一个抽象类,不能直接使用。通常我们使用的是它的直接子类MutableLiveData。
LiveData
是一个数据的包装。具体的包装对象可以是任何数据,包括集合(比如List
)。LiveData
通常在ViewModel
中创建,然后通过getter
方法获取。
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()
}
}
通常情况下都是在组件的onCreate()
方法中开始观察数据,原因有以下两点:
onResume()
方法Activity/Fragment
在处于活跃状态时立刻可以展示数据。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()
}
}
如果想要在UI Controller
中改变LiveData
中的值呢?(比如点击某个Button
把性别从男设置成女)。LiveData
并没有提供这样的功能,但是Architecture Component
提供了MutableLiveData
这样一个类,可以通过setValue(T)
和postValue(T)
方法来修改存储在LiveData
中的数据。MutableLiveData
是LiveData
的一个子类,从名称上也能看出这个类的作用。举个直观点的例子:
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()
可以在子线程中调用。
LiveData还提供了一个名为observeForever()
的方法,使用起来与observe()
没有太大差别。区别在于,当LiveData所包装的数据发生变化时,无论页面处于什么状态,observeForever()
都能收到通知。(正所谓永远观察)因此,在用完之后,一定要记得调用removeObserver()
方法来停止对LiveData的观察,否则LiveData会一直处于激活状态,Activity则永远不会被系统自动回收,这就造成了内存泄漏。
还有两个重要的方法,分别是observe()
方法和setValue()
方法:
@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);
}
/**
* Sets the value. If there are active observers, the value will be dispatched to them.
* 设置值。如果存在活动的观察者,则值将被分派给他们。
*
* 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;
}
// 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
的数据类型。这样对数据库中的任何改动都会被传递出去。这样修改完数据库就能获取最新的数据,减少了主动获取数据的代码。
//我还没学完就不展示了
LiveData
的活跃状态包括:STARTED
或者RESUMED
两种状态。那么如何在活跃状态下把数据传递出去呢?下面是示例代码:
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
:
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
的示例设计成单例的:
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
中像如下这样使用:
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
分发给observers
之前想要修改一下存储在LiveData
中的值,或者你想根据当前的值进行修改返回另一个值。Lifecycle
提供了Transformations
类来通过里面的helper
方法解决这种问题。
Transformations.map()
可以将LiveData
中的数据进行改变:
val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
user -> "${user.name} ${user.lastName}"
}
将LiveData
中的User
数据转换成String
Transformations.switchMap()
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
和这个组件绑定:
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)
,而不是重用上次已经获取到的查询。那么应该怎样避免这个问题呢?看一下下面的代码:
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()
可以减少没有必要的请求。并且同样,只有在观察者处于活跃状态时才会运算并将结果通知观察者。
MediatorLiveData
是LiveData
的子类,可以通过MediatorLiveData
合并多个LiveData
来源的数据。同样任意一个来源的LiveData
数据发生变化,MediatorLiveData
都会通知观察他的对象。说的有点抽象,举个栗子。比如UI
接收来自本地数据库和网络数据,并更新相应的UI
。可以把下面两个LiveData
加入到MeidatorLiveData
中:
关联数据库的LiveData
关联联网请求的LiveData
相应的UI
只需要关注MediatorLiveData
就可以在任意数据来源更新时收到通知。