Jetpack之可观察的数据存储器-LiveData使用篇

2021年03月10日 99 字 Jetpack


LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

【本系列文章(JAVA/KOTLIN)演示案例均存储在github存储库ArchitecturalComponentExample中】

【如需参考LiveData官方文档请移步此处】

LiveData的优势

在正式使用之前,我们先来了解下其优势(即我们为什么要使用它),以下引用取自官方文档:

确保界面符合数据状态

LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知 Observer 对象。您可以整合代码以在这些 Observer 对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。

不会发生内存泄漏

观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。

不会因 Activity 停止而导致崩溃

如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。

不再需要手动处理生命周期

界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

数据始终保持最新状态

如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

适当的配置更改

如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

共享资源

您可以使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。如需了解详情,请参阅扩展 LiveData。

引入依赖

LiveData作为生命周期感知组件,它的依赖包也是 lifecycle族系,通常的,我们常常将LiveData配合ViewModel使用,当然你也可以单独使用LiveData依赖。

1
2
3
4
5
6
7
def lifecycle_version = "2.3.0"

// 如果你需要配合ViewModel使用,请加上
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

此文演示项目为ViewModel+LiveData,如有对ViewModel不了解的可以通过【官方文档】【Jetpack之界面数据存储组件-ViewModel】具体了解。

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

  • 创建 LiveData 的实例以存储某种类型的数据。这通常在 ViewModel 类中完成。
  • 创建可定义 onChanged() 方法的 Observer 对象,该方法可以控制当 LiveData 对象存储的数据更改时会发生什么。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中创建 Observer 对象。
  • 使用 observe() 方法将 Observer 对象附加到 LiveData 对象。observe() 方法会采用 LifecycleOwner 对象。这样会使 Observer 对象订阅 LiveData 对象,以使其收到有关更改的通知。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中附加 Observer 对象。

创建LiveData

创建LiveData的方式非常简单,你只需要直接new出他的子类并传入你想要观察的数据类型泛型即可,LiveData提供setValue(T t) & postValue(T t)用以更新值, 可以使用T getValue()获取值:

1
2
3
4
5
6
7
8
MutableLiveData<Integer> price = new MutableLiveData<>();
// 当然你也可以这样
MutableLiveData<Seller> mSeller = new MutableLiveData<>();
// 定义更新LiveData值的方法
public void countUp() {
// 注意,在初始时LiveData中包含的数据可能为空,你需要为它设置值
price.setValue(price.getValue() == null ? 0 : price.getValue() + 1);
}

观察LiveData

在大多数情况下,应用组件的 onCreate() 方法是开始观察 LiveData 对象的正确着手点,原因如下:

  • 确保系统不会从 Activity 或 Fragment 的 onResume() 方法进行冗余调用。
  • 确保 Activity 或 Fragment 变为活跃状态后具有可以立即显示的数据。一旦应用组件处于 STARTED 状态,就会从它正在观察的 LiveData 对象接收最新值。只有在设置了要观察的 LiveData 对象时,才会发生这种情况。

LiveData订阅observe(@NonNull LifecycleOwner owner, @NonNull Observer observer)需要两个参数,生命周期拥有者LifecycleOwner和观察者Observer。

一般的,继承自AndroidX包中的Activity/Fragment组件(api >= 26)已自行实现LifecycleOwner。

在未强调说明的情况下,本文代码块均保持使用Java8特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 建立订阅关系
viewModel.price.observe(this, this::updatePriceButton);

// 定义Observer.onChanged(T t)回调执行的函数
private void updatePriceButton(int price) {
// 在未强调时,此处均为日志打印,详细代码见Demo
// 后续代码块中将简化或不写类似实现
}

// 创建一个onClick触发LiveData值的更新
public void countUp(View view) {
viewModel.countUp();
}

我们来看一下执行效果:

1
2
3
订阅数据变化👉 0
订阅数据变化👉 1
订阅数据变化👉 2

当我们改变被观察的值时,我们的观察者也如预期被通知到了,这样便实现了最简LiveData数据观察流程(详细代码【Java示例请参见此处】【Kotlin示例请参见此处】)。

扩展LiveData

你可以继承LiveData自行实现LiveData的数据变更状态的分发,已演示项目为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyPriceLiveData extends LiveData<Integer> {
private static final String TAG = "LiveDataExampleLog";
// 此处定义监听,监听触发时调用 setValue(T t)方法
PriceManager.PriceListener listener = this::setValue;

// 同样的你可以将LiveData设置成单例,此处不做实现

@Override
protected void onActive() {
// 此处定义生命周期处于 STARTED 或 RESUMED 状态时的操作
Log.i(TAG, "MyPriceLiveData.onActive");
PriceManager.getInstance().requestPriceUpdate(listener);
}

@Override
protected void onInactive() {
Log.i(TAG, "MyPriceLiveData.onInactive");
// 此处定义没有任何活跃观察者时,解除对PriceManager的关联
PriceManager.getInstance().removeUpdateListener(listener);
}
}

我们像先前一样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ViewModel中
private final PriceManager priceManager = PriceManager.getInstance();
MyPriceLiveData extensionData = new MyPriceLiveData();

public void updatePriceForExtension() {
priceManager.updatePrice(priceManager.price + 1);
}

// Activity/Fragment中
// 订阅LiveData扩展
viewModel.extensionData.observe(this, this::updateExtensionButton);

// 点击事件触发值更新
public void extensionData(View view) {
viewModel.updatePriceForExtension();
}

我们运行查看结果,并做息屏亮屏操作:

1
2
3
4
5
6
7
8
9
// 点击按钮
测试扩展LiveData👉 1
测试扩展LiveData👉 2
// 息屏
MyPriceLiveData.onInactive
// 亮屏
MyPriceLiveData.onActive
// 此处PriceManager未做值未改变时不更新的限制,故会重新分发值变化
测试扩展LiveData👉 2

我们的扩展LiveData会在息屏/亮屏时触发 onInactive()/ onActive()回调,从而执行我们的自定义实现代码。

转换 LiveData

LiveData为我们提供了数据转换功能,包含Transformations.map()Transformations.switchMap()两种方式,其调用方式一致,区别在于 map() 的第二个参数(可理解为转换器)需返回泛型T,而switchMap() 的第二个参数(可理解为转换器)需返回LiveData

map()转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ViewModel中
MutableLiveData<Integer> priceForMap = new MutableLiveData<>();
// 观察price并转换为字符串的可观察对象
LiveData<String> userThink = Transformations.map(priceForMap,
price -> price + (price % 2 == 0 ? "块刚刚好" : "块贵了"));
// 更新price值
public void setPriceForMap() {
priceForMap.setValue(priceForMap.getValue() == null ? 0 : priceForMap.getValue() + 1);
}

// Activity/Fragment中使用
// 数据转换 订阅价格的转换 => 用户价格感知
viewModel.userThink.observe(this, this::updateUserThinkButton);

我们执行观察结果:

1
2
3
转换数据(map)👉 0块刚刚好
转换数据(map)👉 1块贵了
转换数据(map)👉 2块刚刚好

switchMap()转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 转换前的LiveData
MutableLiveData<Integer> priceForSwitch = new MutableLiveData<>();
// 转换后的目标LiveData
MutableLiveData<String> sellerThink = new MutableLiveData<>();

private MutableLiveData<String> getSellerThink(int price) {
String think = price + (price <= 3 ? "穷鬼" : "冤大头");
sellerThink.setValue(think);
return sellerThink;
}
// 更新price值
public void setPriceForSeller() {
priceForSwitch.setValue(priceForSwitch.getValue() == null ? 0 : priceForSwitch.getValue() + 1);
}
// 转换结果LiveData
LiveData<String> switchToUserLook = Transformations.switchMap(priceForSwitch, this::getSellerThink);

调用方式与map()一致,你可以对转换后的LiveData观测,也可以对转换结果LiveData观测,此处省略,我们来观察其执行结果:

1
2
3
4
我是商家想法的订阅:0穷鬼
转换数据(switchMap)👉 0穷鬼
我是商家想法的订阅:1穷鬼
转换数据(switchMap)👉 1穷鬼

对转换后的LiveData观测及以对转换结果LiveData观测均能收到响应。为判断2个LiveData是否观察的是同一个对象,我们将转换后的目标及结果改造为对象,观察其hashCode是否相等:

1
2
3
4
// 转换后的目标LiveData
MutableLiveData<Seller> mSeller = new MutableLiveData<>();
// 转换结果LiveData
LiveData<Seller> switchToSeller = Transformations.switchMap(priceForSeller, this::getSeller);

执行观察结果打印:

1
2
3
订阅的Seller.hashCode() = 84056966
viewModel.mSeller.getValue().hashCode() = 84056966
viewModel.switchToSeller.getValue().hashCode() = 84056966

我们发现,转换后的目标LiveData及转换结果LiveData实际观察对象为同一个,不会为数据创建副本。

合并LiveData

MediatorLiveData 是 LiveData 的子类,允许您合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。

首先我们需要定义一个合并LiveData源:

1
MediatorLiveData<Object> mediatorLiveData = new MediatorLiveData<>();

并在使用时,为其添加观察的LiveData源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 合并观察1
viewModel.mediatorLiveData.addSource(viewModel.switchToSeller, this::onMediatorData);
// 合并观察2
viewModel.mediatorLiveData.addSource(viewModel.userThink, this::onMediatorData);
// 观察数据合并
viewModel.mediatorLiveData.observe(this,this::onMediatorData);

boolean mediatorFlag = false;

// 触发不同的数据更新
public void mediatorData(View view) {
if (mediatorFlag) {
viewModel.setPriceForSeller();
} else {
viewModel.setPriceForMap();
}
mediatorFlag = !mediatorFlag;
}

我们观察其执行结果:

1
2
3
4
5
6
7
// 第一次触发
转换数据(map)👉 0块刚刚好
合并数据👉 0块刚刚好
// 第二次触发
我是商家想法的订阅:0穷鬼
合并数据👉 0穷鬼
转换数据(switchMap)👉 0穷鬼

我们发现,在触发单个LiveData值变化时除了其本身LiveData的观察者被触发,合并数据的观察者也会被触发。

对于此API的应用场景,官方有提出以下场景:

例如,如果界面中有可以从本地数据库或网络更新的 LiveData 对象,则可以向 MediatorLiveData 对象添加以下源:

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

您的 Activity 只需观察 MediatorLiveData 对象即可从这两个源接收更新。

小节

LiveData 是生命周期相关的数据观察者,你可以通过自定义LiveData、使用map()及switchMap()转换LiveData源为你需要的LiveData源、你也可以合并观察不同的LiveData源的变化,这些方式基本能满足对一般数据观察的需求。