翻译官方文档LiveData
官方文档链接:https://developer.android.google.cn/topic/libraries/architecture/livedata.html
1.前言
前面讲到了AAC框架对于生命周期的支持,这一篇来看看框架自己提供的可感知生命周期的类。除了学会怎么使用它,还能增加对框架的核心Lifecycle的认识。
2.使用案例
LiveData这个类持有数据并允许数据被观察。不同于普通的被观察者,它可以被指定组件的Lifecycle,按照该生命周期提供观察的通知。LiveData认为,只有当观察者绑定的Lifecycle.State处于STARTED或者RESUMED时,观察者才是活跃的(可以发送通知)。
public class LocationLiveData extends LiveData<Location> {
private LocationManager locationManager;
private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};
public LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
}
@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}
@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}
上面实现位置服务的监听有三个需注意的地方:
-
onActive()
,当LiveData有一个活跃的观察者时就会被调用。可以在这里开始观察设备位置的更新。 -
onInactive()
,当LiveData连一个活跃的观察者都没有时就会被调用。此时没有必要与位置服务保持连接,因为保持连接会消耗大量的电量而且没任何作用。 -
setValue()
,调用此方法更新LiveData中的数据,并通知活跃的观察者发生的改变。
LocationLiveData可以按照以下方式使用:
public class MyFragment extends LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LiveData<Location> myLocationListener = ...;
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.observe(this, location -> {
// update UI
});
}
});
}
}
observe()
方法传入LifecycleOwner对象作为第一个参数,意味着之后传入的观察者将绑定到Lifecycle上:
- 如果Lifecycle不在活跃状态(STARTED或RESUMED),当值发生改变后,观察者并不会收到通知。
- 如果Lifecycle被销毁了,观察者将自动从观察队列中移除。
3.生命周期感知
实际上,LiveData的生命周期感知特性提供了新的选择:在Activity、Fragment等多个组件间共享数据。下面的例子为了简单,使用了单例模式:
public class LocationLiveData extends LiveData<Location> {
private static LocationLiveData sInstance;
private LocationManager locationManager;
@MainThread
public static LocationLiveData get(Context context) {
if (sInstance == null) {
sInstance = new LocationLiveData(context.getApplicationContext());
}
return sInstance;
}
private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};
private LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
}
@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}
@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}
现对上面的Fragment进行如下改写:
public class MyFragment extends LifecycleFragment {
public void onActivityCreated (Bundle savedInstanceState) {
Util.checkUserStatus(result -> {
if (result) {
LocationLiveData.get(getActivity()).observe(this, location -> {
// update UI
});
}
});
}
}
即使有多个Activity和Fragment在观察LocationLiveData对象,但仍能做到规范地管理,比如,至少有一个观察者是活跃的,才会连接到系统服务。所以,使用LiveData有以下好处:
- 不会内存泄漏:既然观察者绑定到了自己的Lifecycle对象上,那么当生命周期结束时,会被自动清理。
- 不会因为Activity停止而崩溃:如果观察者的Lifecycle不是活跃状态(比如在回退栈中),将不会收到改变的事件。
- 始终保持数据最新:如果Lifecycle重新启动(比如,Activity从回退栈中恢复到STARTED状态),观察者将接收到最新的位置信息(除非还没有)。
- 正确处理配置更改:如果Activity或Fragment由于配置更改(比如,旋转屏幕)而重建,观察者立刻接收到最新有效的位置信息。
- 共享资源:可以保持单个LocationLiveData实例,只连接到系统服务一次,并且正确支持应用中所有的观察者。
- 不需要手动处理生命周期:也许会注意到,例子中的Fragment仅在想要时观察数据,不用担心生命周期结束后,仍在观察或才开始观察。Lifecycle会自动管理所有的一切,只要Fragment在观察时提供了自己的Lifecycle。
4.LiveData转换
有时候,希望在给观察者分发数据之前改变LiveData的值,或者根据不同的值返回不同的LiveData对象。Lifecycle包提供了Transformations类,包含有助于执行这些操作的方法。
-
Transformations.map()
适用于传入改变LiveData值的方法,并将结果向下游传递。
LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });
-
Transformations.switchMap()
与map()方法类似,适用于传入改变LiveData的值再封装起来的方法,并将结果向下游传递。注意,传入的方法返回的LiveData对象必须包含Lifecycle。
private LiveData<User> getUser(String id) { ...; } LiveData<String> userId = ...; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
这些转换操作允许在调用链上携带传递观察者绑定的Lifecycle信息,当有观察者观察转换后的LiveData时,才真正开始执行这些转换(类似响应式编程)。这种方式允许隐式传递生命周期相关的行为,不需要添加显式的调用和依赖。任何时候,在ViewModel中需要一个Lifecycle对象,转换操作有可能解决问题。
举个例子,假设有个界面,用户输入一个地址,将收到该地址对应的邮编。
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
如果这样实现,界面需要在每次调用getPostalCode()
方法时,先从之前的LiveData对象中注销观察者,再注册到新的LiveData对象中。此外,当界面重建时,将会重新调用getPostalCode()
方法,得到的对象也不是之前调用的结果。建议实现一个从输入的地址到邮编信息的转换,来取代上面的方法。
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
private final MutableLiveData<String> addressInput = new MutableLiveData();
public final LiveData<String> postalCode =
Transformations.switchMap(addressInput, (address) -> {
return repository.getPostCode(address);
});
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository
}
private void setInput(String address) {
addressInput.setValue(address);
}
}
成员属性postalCode对象不会改变索引,可以使用
public final
修饰。定义了基于addressInput对象的转换,当它发生改变且有活跃的观察者时,将会调用getPostalCode()
方法。若当时没有活跃的观察者,不会执行任何转换直到加入一个观察者。
这种机制允许在应用底层创建LiveData对象,并满足响应式地运行。而ViewModel能容易地获取它们,并基于它们定义转换规则。
5.创建新的转换
应用可能需要十几种不同类型地特定转换,但是默认没有提供。可以使用MediatorLiveData类来实现自己所需的转换,因为它是为了正确监听其它LiveData实例,并处理它们发出的事件而专门创建的类。MediatorLiveData关注于正确传递活跃/非活跃状态给原始的LiveData。
6.总结
LiveData目的就是为了控制反转(IOC),实时地反馈数据的变化来驱动界面的更新。因为数据变化的发生不受界面控制,若不加入对界面生命周期的监听,容易出现操作的发生与界面的生命周期不符。