Android JetPack - LiveData

LiveData overview

LiveData 是一个 observable 数据的持有类。与通常的 observable 不同的是, LiveData 具有生命周期意识,意思就是它遵循其他应用组件的生命周期,例如 activity , fragment , service 。这就可以确保 LiveData 仅在应用组件处于活跃状态的时候更新 observer 。

LiveData 可以被认作是观察者,并且仅在它的生命周期处于 STARTED 或者 RESUMED 的时候活跃。 LiveData 仅通知活跃的观察者。已注册但是处于非活跃状态的观察者不会收到通知。

你可以注册与实现了 LifecycleOwner 接口的对象相匹配的观察者。这种关系允许在相应的 Lifecycle 对象更改为 DESTROYED 的时候移出观察者。这对于 Activity 和 Fragment 特别有用,因为他们可以安全的观察 LiveData 对象而不用担心内存泄漏 - 因为 Acticity 和 Fragment 在其生命周期被销毁的时候会取消订阅。

The advantages of using LiveData

使用 LiveData 有着以下的优点:

  • 确保你的 UI 跟数据的状态相匹配
    LiveData 遵循着观察者模式。 当生命周期变化的时候,LiveData 会通知 Observer 对象。在这些 Observer 对象中,你需要整理更新 UI 的代码。你的观察者得在每次数据有变化的时候更新 UI 而不是每次都全部更新一遍 UI 。
  • 没有内存泄漏
    Observer 被绑定在 Lifecycle 对象上,当生命周期对象被销毁的时候也会清理自己。
  • 不会因为停止了的 Activity 而发生崩溃
    当观察者的生命周期处于非活跃的状态时,比如说 Activity 处于回退栈时,他们不再接受任何 LiveData 的事件。
  • 不用再去手动管理生命周期
    UI 组件仅仅是观察相关数据,不会停止或者继续观察。 LiveData自动管理这些,因为它在观察的时候是有生命周期状态的。
  • 数据始终保持最新
    如果生命周期变为非活动状态,则会在变为活动状态的时候接受最新的数据。例如,后台的 Activity 在返回迁台后立即受到最新数据。
  • 适当的配置更改
    如果一个 Activity 或者 Fragment 在配置更改之后被重建,例如设备旋转,就会立即受到最新的数据。
  • 共享资源
    你可以使用单例模式扩展 LiveData 类来包装系统服务,将 LiveData 对象连接到系统服务一次之后,然后任何需要观察该资源的观察者都可以只观察 LiveData 对象了。

Work with LiveData objects

按照以下步骤使用 LiveData 对象:

  1. 创建一个 LiveData 对象来保存某种特定的数据,通常在 ViewModel 类中。
  2. 创建一个 Observer 对象并且定义 onChanged() 方法,这个方法控制着当 LiveData 变化的时候该如何操作。一般都在 UI controller 里面定义 Observer 对象,比如说 Activity 或者 Fragment。
  3. 使用 observe() 方法将 Observer 对象和 LivaData 对象关联起来。 observe() 方法接收一个 LifecycleOwner 对象。这就会将 Observer 对象订阅到 LiveData 对象,以便通知其更改。你通常会将 Observer 对象附加到 UI controller 上,例如 Activity 或者 Fragment。

你可以使用 observeForever() 方法订阅,而无需提供 LifecycleOwner 对象。这种情况下,我们就会认为观察者永远都是活跃的,永远都要收到通知。你可以使用 removeObserver() 方法来移除观察者。

当你更新了存储在 LiveData 对象中的数据的时候,就会出发所有生命周期是活跃的观察者。

LiveData 允许 UI controller 观察数据更新。当 LiveData 持有的数据发生变化的时候, UI 也会自动跟着改变。

Create LiveData objects

LiveData 可以是任何类型的数据的包裹器,包括那些实现了 Collection 的类,比如 List 。 LiveData 对象一般都放在 ViewModel 对象中,通过 getter 方法访问,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 对象中的数据并没有被设置。

Observe LiveData objects

在大多数情况下,在 app 组件的 onCreate() 方法中开始观察 LiveData 对象,原因如下:

  • 可以确保系统不会在 Activity 或者 Fragment 的 onResume() 方法进行冗余的调用。
  • 可以确保 Activity 或者 Fragment 在其变为活跃状态的时候可以立即显示出数据。只要组件进入到了 STARTED 状态,他就从它观察的 LivaData 中拿到最新的数据。当然只有当你在已经观察 LiveData 对象的情况下。

总的来说, LiveData 对象只在数据变化且观察者活跃的情况下才传递数据更新。当然也有例外,在当订阅者从非活跃状态变为活跃状态的时候也会收到更新。此外,如果观察者第二次从非活跃状态变为活跃状态的话,且子上次变为活跃状态以来发生了数据更新,他也会收到。

示例如下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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() 不会被调用。

Update LiveData objects

LiveData 没有公开的方法来更新存储的数据。 MutableLiveData 类公开了 setValue(T)postValue(T) 方法,你必须使用这两个方法类更改储存在 LiveData 中的值。通常情况下, MutableLiveDataViewModel 类中使用,而 ViewModel 类只暴露 finalLiveData 对象给观察者。

在你设置好了观察者关系之后,你就可以更新 LiveData 对象了,下面是点击按钮触发更新的例子:

1
2
3
4
5
6
7
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
mModel.getCurrentName().setValue(anotherName);
}
});

调用 setValue(T) 会使 onChanged() 方法得到调用。例子中使用的是按钮点击,实际情况中,我们可以在各种情况下调用 setValue()postValue() 方法,包括网络响应、数据库读取。总之,调用上述两种方法就可以触发观察者更新 UI 。setValue() 用在主线程, postValue() 用在工作线程。

Use LiveData with Room

Room 持久化库支持 observable 的查询,可以返回 LiveData 对象。 Observable 查询在 DAO 类中编写。

Room 会生成所需代码,在数据库更新的时候更新 LivaData 对象。生成的代码会在需要的时候异步执行。此模式对于保持 UI 数据与数据库存储数据保持同步非常有用。

Extend LiveData

LiveData 将生命周期处于 STARTEDRESUMED 的 observer 看作为活跃状态。下面的示例展示了如何继承 LiveData 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}

上面价格监听器的实现包含了如下几个重要的方法:

  • onActive()LiveData 对象有一个活跃的观察者的时候被调用。这意味着你得在这个方法中开始订阅股票价格更新了。
  • onInactive()LiveData 没有任何的观察者的时候被调用。既然都没有了任何的观察者,就没有理由继续更新股票价格了。
  • setValue(T) 方法在更新 LiveData 对象中的值并且通知所有的活跃的观察者。

你可以这样使用 StockLiveData;

1
2
3
4
5
6
7
8
9
10
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 。这样做就代表着 observer 已经关联到了 Lifecycle 对象上。也就意味着:

  • 如果 Lifecycle 对象处于非活跃状态,那么即使数据发生了改变, observer 也不会被调用。
  • Lifecycle 对象被销毁之后, observer 也自动被移除了。

实际上 LiveData 对象可以跨组件使用。为了使例子更简便,我们把 LiveData 对象实现成了一个单例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
}
}

然后你可以在另一个组件里面使用它:

1
2
3
4
5
6
7
8
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
StockLiveData.get(getActivity()).observe(this, price -> {
// Update the UI.
});
}
}

可以有多个 Fragment 或者 Activity 观察 MyPriceListener 对象。 LiveData 仅在至少有一个订阅者活跃的时候连接到系统服务上。

Transform LiveData

如果你想要在 LiveData 分发给订阅者之前更改其存储的值,或是你想根据某个 LiveData 的值返回另一个 LiveData 对象。 Lifecycle 包提供了 Transformations 类来支持这些种场景。

Transformations.map() :对存储在 LiveData 中的值应用某个函数,并将结果传递给下游。有点流式编程的意思。

1
2
3
4
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});

Transformations.switchMap() :与 map() 类似,将函数应用于存储在 LiveData 对象中的值,将结果解包并调度到下游。传递给 switchMap() 的函数必须返回一个 LiveData 对象,示例如下:

1
2
3
4
5
6
private LiveData<User> getUser(String id) {
...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

你可以在观察者的生命周期内使用转换方法传递信息 。除非观察者正在观察着返回的 LiveData 对象,否则不会进行转换。因为转换是懒惰计算的。所以与生命瘦子相关的行为会被饮食的传递下去,而不需要显示调用或者依赖。

如果你认为你在 ViewModel 对象中需要一个生命周期对象,转换可能是一个比较好的选择。例如,假设你有一个 UI 组件来接受地址并返回那个地址的邮编。你可以为此实现一个简单的 ViewModel ,示例如下:

1
2
3
4
5
6
7
8
9
10
11
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 组件会在每次调用 getPostalCode() 的时候取消注册之前的的 LiveData 对象并且注册新的 LiveData 对象。另外,当 UI 组件重建的时候,又会触发调用 repository.getPostCode() 而不是复用上一次调用的结果。

相反,你可以将查找邮政编码是纤维地址输入的转换,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 字段是 publicfinal 的,因为这个字段的兑现根本不需要改变。 postalCode 字段定义为 addressInput 的转换,意味着当 addressInput 改变的时候才去调用 repository.getPostCode() 。这是在由活跃的款茶者的情况下进行的,如果在调用 repository.getPostCode() 的时候没有活跃的观察者,就不进行任何的计算。

这个机制可以让你降低创建 LiveData 对象的级别,按需进行计算。 ViewModel 可以轻松获取 LiveData 对象的引用,然后在他们之上定义转换规则。

Create new transformations

在你的应用中可能会需要好多种转换,但默认情况下不提供他们。要实现自己的转换,你可以使用 MediatorLiveData 类,用它去监听另外的 LiveData 对象,并且在他们发出时间的时候进行转换。 MediatorLiveData 会正确的将其状态向上传递到源 LiveData 对象。

Merge multiple LiveData sources

MediatorLiveData 类是 LiveData 类的子类,允许你合并多个 LiveData 源,只要任何原始的 LiveData 源发生变化,就会出发 MediatorLiveData 的观察者。
例如,在你的 UI 中有一个可以从本地数据库或者网络更新的 LiveData 对象,则可以将以下源添加到 MeditaorLiveData 对象:

  • 与存储在数据库中的数据关联的 LiveData 对象
  • 与从网络访问的数据关联的 LiveData 对象

你的 Activity 只需要观察 MediatorLiveData 对象来同时从以上两个源来接收更新。