(五)LiveData
概述
- LiveData是一个可观察的数据持有者类。
- 与常规observable不同,LiveData是生命周期感知的,这意味着它尊重其他应用程序组件的生命周期,例如活动,片段或服务。
此感知确保LiveData仅更新处于活动的生命周期状态的应用程序组件观察者。
如果Observer类表示的观察者生命周期处于STARTED
或RESUMED
状态,LiveData会认为它处于活动状态。
LiveData仅通知活动观察者有关更新的信息。 注册观看LiveData对象的非活动观察者不会收到有关更改的通知。
您可以注册与实现LifecycleOwner接口的对象配对的观察者。 此关系允许在相应Lifecycle对象的状态更改为DESTROYED时删除观察者。 这对于Activity和Fragment特别有用,因为它们可以安全地观察LiveData对象而不用担心泄漏 - Activity和Fragment在其生命周期被破坏时立即取消订阅。
一、使用LiveData的优点
确保UI符合数据状态
LiveData遵循观察者模式。 生命周期状态更改时,LiveData会通知Observer对象。 您可以合并代码以更新这些Observer对象中的UI。
没有内存泄漏
观察者绑定到Lifecycle对象并在其相关生命周期被破坏后自行清理。
由于停止活动而没有崩溃
如果观察者的生命周期处于非活动状态(例如,在后端堆栈中的活动的情况下),则它不会接收任何LiveData事件。
不再需要手动生命周期处理
UI组件只是观察相关数据,不会停止或恢复观察。 LiveData自动管理所有这些,因为它在观察时意识到相关的生命周期状态变化。
始终保持最新数据
如果生命周期变为非活动状态,则会在再次变为活动状态时接收最新数据。 例如,后台活动在返回前台后立即收到最新数据。
适当的配置更改
如果由于配置更改(例如设备轮换)而重新创建Activity或Fragment,则会立即接收最新的可用数据。
共享资源
您可以使用单例模式扩展LiveData对象以包装系统服务,以便可以在应用程序中共享它们。 LiveData对象连接到系统服务一次,然后任何需要该资源的观察者都可以只观看LiveData对象。
二、使用LiveData对象
请按照以下步骤使用LiveData对象:
- 创建LiveData实例以保存特定类型的数据。 这通常在您的ViewModel类中完成。
- 创建一个Observer对象,该对象定义
onChanged()
方法,该方法控制LiveData对象保持数据更改时发生的情况。 您通常在UI控制器中创建一个Observer对象,例如Activity或Fragment。 - 使用
observe()
方法将Observer对象附加到LiveData对象。observe()
方法采用LifecycleOwner对象。 这会将Observer对象订阅到LiveData对象,以便通知它更改。 您通常将Observer对象附加到UI控制器中,例如Activity或Fragment。
注意:您可以使用
observeForever(Observer)
方法注册没有关联的LifecycleOwner对象的观察者。 在这种情况下,观察者被认为始终处于活动状态,因此始终会收到有关修改的通知。 您可以删除这些调用removeObserver(Observer)方法的观察者。
更新存储在LiveData对象中的值时,只要附加的LifecycleOwner处于活动状态,它就会触发所有已注册的观察者。
LiveData允许UI控制器观察者订阅更新。 当LiveData对象保存的数据发生更改时,UI会自动更新响应。
创建LiveData对象
- LiveData是一个包装器,可以与任何数据一起使用,包括实现集合的对象,例如List。
- LiveData对象通常存储在ViewModel对象中,并通过getter方法访问,如以下示例所示:
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<String>();
}
return mCurrentName;
}
// Rest of the ViewModel...
}
最初,未设置LiveData对象中的数据。
注意:确保存储更新ViewModel对象中的UI的LiveData对象,而不是活动或片段,原因如下:
- 避免臃肿的Activityb和Fragment。 现在,这些UI控制器负责显示数据但不保持数据状态。
- 将LiveData实例与特定Activity或Fragment实例分离,并允许LiveData对象在配置更改后继续存在。
观察LiveData对象
在大多数情况下,app组件的onCreate()
方法是开始观察LiveData对象的正确位置,原因如下:
- 确保系统不会从Activity和Fragment的
onResume()
方法进行冗余调用。 - 确保活动或片段具有可在其变为活动状态时立即显示的数据。 一旦应用程序组件处于
STARTED
状态,它就会从它正在观察的LiveData对象中接收最新值。 只有在设置了要观察的LiveData对象时才会出现这种情况。
通常,LiveData仅在数据更改时才传递更新,并且仅在活动观察者时传递更新。 此行为的一个例外是观察者在从非活动状态更改为活动状态时也会收到更新。 此外,如果观察者第二次从非活动状态更改为活动状态,则只有在自上次活动状态以来该值发生更改时才会收到更新。
以下示例代码说明了如何开始观察LiveData对象:
public class NameActivity extends AppCompatActivity {
private NameViewModel mModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
mModel = ViewModelProviders.of(this).get(NameViewModel.class);
// Create the observer which updates the UI.
final Observer<String> nameObserver = new Observer<String>() {
@Override
public void onChanged(@Nullable final String newName) {
// Update the UI, in this case, a TextView.
mNameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getCurrentName().observe(this, nameObserver);
}
}
在使用nameObserver作为参数传递调用observe()
之后,立即调用onChanged()
,提供存储在mCurrentName中的最新值。 如果LiveData对象未在mCurrentName中设置值,则不会调用onChanged()
。
更新LiveData对象
- LiveData没有公开的方法来更新存储的数据。
- MutableLiveData类公开
setValue(T)
和postValue(T)
方法,如果需要编辑存储在LiveData对象中的值,则必须使用这些方法。 - 通常在ViewModel中使用MutableLiveData,然后ViewModel仅向观察者公开不可变的LiveData对象。
- 设置观察者关系后,可以更新LiveData对象的值,如以下示例所示,当用户点击按钮时触发所有观察者:
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
mModel.getCurrentName().setValue(anotherName);
}
});
在示例中调用setValue(T)
会导致观察者使用值"John Doe"调用其onChanged()
方法。 该示例显示按下按钮,但可以调用setValue()
或postValue()
以更新mName,原因有多种,包括响应网络请求或数据库负载完成; 在所有情况下,对setValue()
或postValue()
的调用都会触发观察者并更新UI。
注意:必须调用
setValue(T)
方法才能从主线程更新LiveData对象。 如果代码在工作线程中执行,则可以使用postValue(T)
方法来更新LiveData对象。
与Room一起使用LiveData
Room持久性库支持可观察的查询,这些查询返回LiveData对象。 可观察查询作为数据库访问对象(DAO)的一部分编写。
在更新数据库时,Room会生成更新LiveData对象所需的所有代码。 生成的代码在需要时在后台线程上异步运行查询。 此模式对于使UI中显示的数据与存储在数据库中的数据保持同步非常有用。
三、扩展LiveData
如果观察者的生命周期处于STARTED
或RESUMED
状态,LiveData会将观察者视为处于活动状态。
以下示例代码说明了如何扩展LiveData类:
public class StockLiveData extends LiveData<BigDecimal> {
private StockManager mStockManager;
private SimplePriceListener mListener = new SimplePriceListener() {
@Override
public void onPriceChanged(BigDecimal price) {
setValue(price);
}
};
public StockLiveData(String symbol) {
mStockManager = new StockManager(symbol);
}
@Override
protected void onActive() {
mStockManager.requestPriceUpdates(mListener);
}
@Override
protected void onInactive() {
mStockManager.removeUpdates(mListener);
}
}
此示例中价格监听器的实现包括以下重要方法:
- 当LiveData对象具有活动观察者时,将调用
onActive()
方法。 这意味着您需要从此方法开始观察股票价格更新。 - 当LiveData对象没有任何活动观察者时,将调用
onInactive()
方法。 由于没有观察者正在收听,因此没有理由保持与StockManager服务的连接。 -
setValue(T)
方法更新LiveData实例的值,并通知任何活动观察者有关更改的信息。
您可以使用StockLiveData类,如下所示:
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LiveData<BigDecimal> myPriceListener = ...;
myPriceListener.observe(this, price -> {
// Update the UI.
});
}
}
observe()
方法将片段(LifecycleOwner的一个实例)作为第一个参数传递。 这样做表示此观察者绑定到与所有者关联的Lifecycle对象,这意味着:
- 如果Lifecycle对象未处于活动状态,则即使值发生更改,也不会调用观察者。
- 销毁Lifecycle对象后,会自动删除观察者。
LiveData对象具有生命周期感知这一事实意味着您可以在多个活动,片段和服务之间共享它们。
为了简化示例,您可以将LiveData类实现为单例,如下所示:
public class StockLiveData extends LiveData<BigDecimal> {
private static StockLiveData sInstance;
private StockManager mStockManager;
private SimplePriceListener mListener = new SimplePriceListener() {
@Override
public void onPriceChanged(BigDecimal price) {
setValue(price);
}
};
@MainThread
public static StockLiveData get(String symbol) {
if (sInstance == null) {
sInstance = new StockLiveData(symbol);
}
return sInstance;
}
private StockLiveData(String symbol) {
mStockManager = new StockManager(symbol);
}
@Override
protected void onActive() {
mStockManager.requestPriceUpdates(mListener);
}
@Override
protected void onInactive() {
mStockManager.removeUpdates(mListener);
}
}
您可以在片段中使用它,如下所示:
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
StockLiveData.get(symbol).observe(this, price -> {
// Update the UI.
});
}
}
多个片段和活动可以观察MyPriceListener实例。 LiveData仅在系统服务中的一个或多个可见且处于活动状态时才连接到系统服务。
四、转换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对象中的值,并将结果解包并调度到下游。 传递给switchMap()的函数必须返回一个LiveData对象,如以下示例所示:
private LiveData<User> getUser(String id) {
...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
您可以使用转换方法在观察者的生命周期中传递信息。 除非观察者正在观察返回的LiveData对象,否则不会计算转换。 因为转换是懒惰地计算的,所以与生命周期相关的行为被隐式传递下去,而不需要额外的显式调用或依赖。
如果您认为在ViewModel对象中需要Lifecycle对象,则转换可能是更好的解决方案。 例如,假设您有一个接受地址的UI组件并返回该地址的邮政编码。 您可以为此组件实现朴素的ViewModel,如以下示例代码所示:
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);
}
}
然后,UI组件需要从先前的LiveData对象取消注册,并在每次调用
getPostalCode()
时注册到新实例。 此外,如果重新创建UI组件,它将触发另一个对repository.getPostCode()
方法的调用,而不是使用前一个调用的结果。
相反,您可以将邮政编码查找实现为地址输入的转换,如以下示例所示:
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,因为该字段永远不会更改。 postalCode字段被定义为addressInput的转换,这意味着当addressInput更改时将调用repository.getPostCode()方法。 如果存在活动的观察者,则如果在调用repository.getPostCode()时没有活动的观察者,则在添加观察者之前不进行任何计算。
此机制允许较低级别的应用程序创建按需延迟计算的LiveData对象。 ViewModel对象可以轻松获取对LiveData对象的引用,然后在它们之上定义转换规则。
创建新的转换
在您的应用中有十几种不同的特定转换可能很有用,但默认情况下不提供它们。 要实现自己的转换,可以使用MediatorLiveData类,该类侦听其他LiveData对象并处理它们发出的事件。 MediatorLiveData正确地将其状态传播到源LiveData对象。
五、合并多个LiveData源
MediatorLiveData是LiveData的子类,允许您合并多个LiveData源。 只要任何原始LiveData源对象发生更改,就会触发MediatorLiveData对象的观察者。
例如,如果UI中有可以从本地数据库或网络更新的LiveData对象,则可以将以下源添加到MediatorLiveData对象:
- 与存储在数据库中的数据关联的LiveData对象。
- 与从网络访问的数据关联的LiveData对象。
您的活动只需要观察MediatorLiveData对象以从两个源接收更新。