LiveData 是一个可观察的数据持有者类。与常规observable不同,LiveData是生命周期感知的,这意味着它尊重其他应用程序组件的生命周期,例如activities, fragments, 或者 services。此感知确保 LiveData 仅更新处于活动生命周期状态的应用程序组件观察者。
LiveData 作为一个观察者,将表现出 Observer 类的特性,如果它的生命周期处于 STARTED 状态,或者 RESUMED 状态它便处于活跃状态。LiveData 仅通知活动观察者有关更新的信息。注册观察 LiveData 对象的非活动观察者 不会收到有关更新的通知。
您可以注册一个与实现 LifecycleOwner 接口的对象配对的观察者 。这种关系允许观察者被移除,当相应的 Lifecycle 对象的生命周期状态变化为 DESTROYED 。这对于activities 和 fragments 特别有用,因为它们可以安全地观察LiveData对象而不用担心泄漏 --- 活动和片段在其生命周期被破坏时立即取消订阅。
使用 LiveData 的优点
使用LiveData具有以下优势:
确保你的UI与你的数据状态相吻合
LiveData遵循观察者模式。Observer 生命周期状态更改时,LiveData 会通知对象。你可以巩固你的代码以更新这些 Observer
对象中的UI 。不是每当你的APP数据更新时更新你的UI,而是每当有一个变化你的观察者可以更新UI。
没有内存泄漏
观察者绑定 Lifecycle 对象并在其相关生命周期被破坏后自行清理。
停止活动不会导致崩溃
如果观察者的生命周期是不活动的,例如在后堆栈中的活动,那么它不会接收任何 LiveData 事件。
不再需要手动处理生命周期
UI组件只观察相关数据,不停止或恢复观察。 LiveData 自动管理所有这些,因为它在观察过程中知道相关的生命周期状态变化。
总是最新的数据
如果一个生命周期变为不活动的,它将在再次活动时接收最新的数据。 例如,在后台的活动在返回到前台后立即接收最新的数据。
自适应配置的更改
如果某个活动或片段由于配置更改(如设备旋转)而重新创建,它将立即接收最新可用数据。
资源共享
您可以使用singleton模式扩展 LiveData 对象来包装系统服务,以便在您的应用程序中共享它们。LiveData
对象连接到系统服务一次,然后任何需要该资源的观察者都可以查看 LiveData
对象。
使用 LiveData 对象
按照以下步骤使用 LiveData 对象:
- 1.创建
LiveData
实例来保存特定类型的数据。这通常在 ViewModel 类中完成。 - 2.创建一个观察者对象,该对象定义 onChanged() 方法,该方法控制
LiveData
对象持有的数据更改时发生的情况。您通常在UI控制器中创建一个观察者对象,例如一个 activity 或者 fragment。 - 3.使用 observe() 方法将观察者对象附加到
LiveData
对象。observe()
方法接受 LifecycleOwner 对象。 订阅观察者对象到LiveData
对象,以便在发生更改时通知它。通常将观察者对象附加到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> currentName;
public MutableLiveData<String> getCurrentName() {
if (currentName == null) {
currentName = new MutableLiveData<String>();
}
return currentName;
}
// Rest of the ViewModel...
}
最初,LiveData
对象中的数据没有设置。
注意:确保将更新UI的 LiveData 对象存储在 ViewModel 对象中,而不是存储在活动或片段中,原因如下: 1.为了避免膨胀的活动和碎片。现在这些UI控制器负责显示数据,而不是保存数据状态。 2.将LiveData实例与特定的活动或片段实例解耦,并允许LiveData对象在配置更改后仍然存在。
观察 LiveData 对象
在大多数情况下,应用程序组件的 onCreate() 方法是开始观察 LiveData 对象的合适位置,原因如下:
- 以确保系统不会从 activity 或 fragment 的 onResume() 方法发出冗余调用。
- 确保活动或片段具有数据,可以在活动时立即显示这些数据。一旦应用程序组件处于启动状态,它就会从它所观察的
LiveData
对象接收到最近的值。只有在设置了要观察的LiveData
对象时才会发生这种情况。
通常,LiveData
只在数据发生更改时提供更新,并且只向活动观察者提供更新。
这种行为的一个例外是,当观察者从非活动状态更改为活动状态时,也会收到更新。
此外,如果观察者第二次从非活动状态更改为活动状态,则只有当值自上次活动以来发生更改时,才会接收到更新。
下面的示例代码演示了如何开始观察 LiveData
对象:
public class NameActivity extends AppCompatActivity {
private NameViewModel model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
model = 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.
nameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.getCurrentName().observe(this, nameObserver);
}
}
使用传递的参数 nameObserver
调用 observe() 后,立即调用 onChanged() ,提供存储在 mCurrentName
中的最新值。
如果 LiveData
对象没有在 mCurrentName
中设置值,则不会调用 onChanged()
。
更新 LiveData 对象
LiveData 没有公开可用的方法来更新存储的数据。MutableLiveData 类公开了 setValue(T) 和 postValue(T) 方法,如果需要编辑存储在 LiveData 对象中的值,则必须使用这些方法。
通常在 ViewModel 中使用 MutableLiveData ,然后 ViewModel
只向观察者公开不可变的 LiveData
对象。
设置好观察者关系后,就可以更新 LiveData
对象的值,如下面的例子所示,当用户点击一个按钮时,它就会触发所有的观察者:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
model.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中显示的数据与数据库中存储的数据同步非常有用。您可以在Room persistent library指南中阅读关于 Room 和 DAOs 的更多信息。
与 LiveData 一起使用 coroutines
LiveData
包括对 Kotlin 协程的支持。有关更多信息,请参见 使用带有Android体系结构组件的Kotlin协程 。
继承 LiveData
如果观察者的生命周期处于 STARTED 或者 RESUMED 状态,LiveData 认为观察者处于活动状态,下面的示例代码演示了如何扩展 LiveData 类:
public class StockLiveData extends LiveData<BigDecimal> {
private StockManager stockManager;
private SimplePriceListener listener = new SimplePriceListener() {
@Override
public void onPriceChanged(BigDecimal price) {
setValue(price);
}
};
public StockLiveData(String symbol) {
stockManager = new StockManager(symbol);
}
@Override
protected void onActive() {
stockManager.requestPriceUpdates(listener);
}
@Override
protected void onInactive() {
stockManager.removeUpdates(listener);
}
}
本例中 price listener
的实现包括以下重要方法:
- 当
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() 方法通过fragment,第一个参数为 LifecycleOwner 的实例,这样做意味着这个观察者被绑定到与所有者关联的 Lifecycle 对象上,这意味着:
- 如果 Lifecycle 对象没有处于活动状态,那么即使值发生了更改,也不会调用观察者。
- 销毁生命周期对象后,将自动删除观察者。
LiveData
对象是生命周期感知的,这意味着您可以在多个活动、片段和服务之间共享它们。
为了保持示例的简单性,可以将 LiveData
类实现为单例,如下所示:
public class StockLiveData extends LiveData<BigDecimal> {
private static StockLiveData sInstance;
private StockManager stockManager;
private SimplePriceListener listener = 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) {
stockManager = new StockManager(symbol);
}
@Override
protected void onActive() {
stockManager.requestPriceUpdates(listener);
}
@Override
protected void onInactive() {
stockManager.removeUpdates(listener);
}
}
你可以在 fragment 中如下使用:
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
StockLiveData.get(symbol).observe(this, price -> {
// Update the UI.
});
}
}
多个片段和活动可以观察 MyPriceListener
实例。
LiveData
只在其中一个或多个服务可见且处于活动状态时才连接到系统服务。
Transform LiveData
在将 LiveData 对象发送给观察者之前,您可能希望更改存储在 LiveData
对象中的值,或者您可能需要根据另一个 LiveData
实例的值返回另一个 LiveData
实例。
生命周期包提供 Transformations 类,该类包含支持这些场景的helper方法。
对存储在LiveData对象中的值应用函数,并将结果向下传播。
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
与 map()
类似,将函数应用于存储在 LiveData
对象中的值,并将结果解包并向下分派。
传递给 switchMap()
的函数必须返回 LiveData
对象,如下例所示:
private LiveData<User> getUser(String id) {
...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
您可以使用转换方法在观察者的生命周期中携带信息。除非观察者正在观察返回的 LiveData
对象,否则不会计算转换。因为转换是延迟计算的,所以与生命周期相关的行为是隐式传递的,不需要额外的显式调用或依赖关系。
如果您认为在 ViewModel 对象中需要一个生命周期对象,那么转换可能是一个更好的解决方案。
例如,假设您有一个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
字段被定义为 addressInput
的转换。
只要您的应用程序有一个与 postalCode
字段关联的活动观察者,每当 addressInput
发生更改时,就会重新计算和检索字段的值。
这种机制允许较低级别的应用程序创建按需延迟计算的 LiveData
对象。
ViewModel
对象可以很容易地获得对 LiveData
对象的引用,然后在其上定义转换规则。
创建新的 transformations
在您的应用程序中,有十几个不同的特定转换可能有用,但它们不是默认提供的。要实现自己的转换,可以使用 MediatorLiveData 类,它侦听其他 LiveData 对象并处理它们发出的事件。 MediatorLiveData
正确地将其状态传播到源 LiveData
对象。
要了解关于此模式的更多信息,请参阅 transformation 类的参考文档。
合并多个LiveData源
MediatorLiveData 是 LiveData 的一个子类,它允许您合并多个 LiveData
源。
然后,当任何原始 LiveData
源对象发生更改时,就会触发 MediatorLiveData
对象的观察者。
例如,如果您的UI中有一个 LiveData
对象,它可以从本地数据库或网络更新,那么您可以向 MediatorLiveData
对象添加以下源:
- 与数据库中存储的数据相关联的 LiveData 对象。
- 与从网络访问的数据相关联的 LiveData 对象。
您的活动只需要观察 MediatorLiveData 对象就可以从两个源接收更新。有关详细示例,请参见 附录:公开应用程序体系结构指南的网络状态部分。