Android官方Architecture架构组件使用介绍之LiveData与ViewModel

Android Architecture Components
系列相关继续介绍
前面说了lifecycle,它的存在从而大大地减少了Activity的各系列重写onXX方法。
今天继续介绍与它一同推出的:livedata,viewmodel。

  • livedata:简单来说,他就是有特殊生命周期的一个监听数据变化的对象。使用仅需赋值和监听即可。(可用于数据驱动界面)
  • viewmodel:他是一套与activity及fragment或application各种生命周期为一体的有生命周期的数据存储对象。(能在需要的生命周期自动销毁数据的对象)

livedata

LiveData 是一个用于持有数据并支持数据能被监听(观察)。和传统的观察者模式中的被观察者不一样,LiveData是一个生命周期感知组件,因此观察者可以指定某一个LifeCycle给LiveData,并对数据进行监听。
如果观察者指定LifeCycle处于Started或者RESUMED状态,LiveData会将观察者视为活动状态,并通知其数据的变化。

使用介绍

导入依赖

1
implementation 'android.arch.lifecycle:extensions:1.1.1'

自定义一个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
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);
}
}
  • setValue()
    通过调用这个方法来更新LiveData的数据,并通知处于活动状态的观察者。
  • postValue()
    该方法作用同上,但它是用于在其他线程中赋值时使用。
  • onActive()
    当这个方法被调用时,表示LiveData的观察者数量从0变为了1,这时就我们的位置监听来说,就应该注册我们的事件监听了。
  • onInactive()
    这个方法被调用时,表示LiveData的观察者数量变为了0,既然没有了观察者,也就没有理由再做监听,此时我们就应该将位置监听移除。

使用这个LiveData

只需要支持库升级到26.1.0即可

1
2
3
4
5
6
7
8
9
10
11
12
public class MyFragment extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
LiveData<Location> myLocationListener = new LocationLiveData(this);
myLocationListener.addObserver(this, new Observer<Location>() {// 该this已在26.1.0版本中实现
@Override
public void onChanged(@Nullable Location location) {
// update UI 当定位成功调用setValue(),这边处于onStart周期,该回调便会自动执行
});
}
}

注意上面的addObserver方法,我们将LifeCycleOwner作为第一个参数传递了进去,这表示我们的LocationLiveData将遵照这个Fragment所持有的LifeCycle办事。

  • 如果LifeCycle不在Started或者RESUMED这两个状态,那么观察者将无法接受到数据更新的回调,即使数据发生了变化。
  • 如果LifeCycle销毁了,即生命周期结束,观察者将被自动从LiveData中移除。

进阶版

我们知道他是有生命周期的,那我们可以做一个定位,实现多个fragment或者activity监听么?

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
34
35
36
37
38
public class LocationLiveData extends LiveData<Location> {

//这里我们将它定义为一个单例
private static LocationLiveData sInstance;
//(这里使用单例的原因就是让多个Activity或者Fragment共享一个LocationLiveData实例。)

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);
}
}

然后,再各个需要的地方都能共用了,并且生命周期也是安全的额✌

高级使用

LiveData的转换

有时候有这样的需求,需要在LiveData将变化的数据通知给观察者前,改变数据的类型;或者是返回一个不一样的LiveData。

  • Transformations.map()

比如将User数据转换为String:

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

用于事件流的传递,用于触发下游数据。

  • Transformations.switchMap()

比如通过userId数据获取User对象

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,会导致不断注册,销毁,是不是也有问题呢?

网络上了解的可以尝试这种方式,

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访问限制符写成public final,因为它将始终不变,UI只要在需要用的时候将观察者注册到postalCode中就行。这是当用户调用setInput后,如果postalCode上有可活动的观察者,那么repository.getPostCode(address)就会被调用,如果此时没有可活动的观察者,则repository.getPostCode(address)不会被调用。

LiveData的特点

  • 没有内存溢出
    当观察者被绑定他们对应的LifeCycle以后,页面销毁自动被溢出,不会导致内存溢出。

  • 不会因为Activity的不可见导致Crash
    当Activity不可见时,即使有数据变化,LiveData也不会通知观察者。因为此时观察者的LifeCyele并不处于Started或者RESUMED状态。

  • 配置的改变
    当前Activity配置改变(如屏幕方向),导致重新从onCreate走一遍,这时观察者们会立刻收到配置变化前的最新数据。

  • 资源共享
    我们只需要一个LocationLivaData,连接系统服务一次,就能支持所有的观察者。

  • 不再有人为生命周期处理
    通过上面的代码可以知道,我们的Activity或者Fragment只要在需要观察数据的时候观察数据即可,不需要理会生命周期变化了。这一切都交给LiveData来自动管理。

其他

  • MediatorLiveData
    暂未去了解,用于创建新的转换。

在应用程序中可能会用到十几种不同的特定转换,但是默认是不提供的。可以使用 MediatorLiveData 实现自己的转换,MediatorLiveData 是为了用来正确的监听其它 LiveData 实例并处理它们发出的事件而特别创建的。MediatorLiveData 需要特别注意正确的向源 LiveData 传递其处于活动/闲置状态。

源码解析

这个,我想应该有比我分析得更好的,推荐阅读下面的源码解析:

viewmodel

  • activity被销毁了,数据丢了怎么办?
  • 异步操作完成,界面已被销毁,出现异常怎么办?
  • 一个activity或fragment,里面的代码执行的事物,简直比上帝还忙,如果你要作一些单元测试,你的代码可以剥离得出来么?
    你可能会想到MVC,MVP这类设计模式,ViewModel类的功能就有点和它类似了。将数据从UI中剥离出来,
    同时他还自带生命周期,比MVP等等模式更方便好用。

使用介绍

第一步:上一个简单的自定义ViewModel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyViewModel extends ViewModel {
private MutableLiveData<List<Data>> datas;
public LiveData<List<Data>> getDatas() {
if (datas == null) {
datas = new MutableLiveData<List<Data>>();
loadDatas();
}
return datas;
}

private void loadDatas() {
// do async operation to fetch users
}

@Override
protected void onCleared() {
super.onCleared();
}
}

注意:

  • 这是当MyActivity被重构时,获得到的model实例是与重构前同一个,当MyActivity被销毁时,Framework会调用ViewModel的onCleared(),我们就可以在此方法中做资源的清理。
  • 因为ViewModel的生命周期是和Activity或Fragment分开的,所以在ViewModel中绝对不能引用任何View对象或者任何引用了Activity的Context的对象。如果ViewModel中需要Application的Context的话,可以继承AndroidViewModel。

第二步:在需要的ativity或fragment中获取ViewModel

1
2
3
4
5
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(-activity或fragment对象-).get(MyViewModel.class);
}
}

注意:这里of后面的参数,如果是同一个对象,那么获取到的MyViewModel也是同一个。即是,可以共享该数据,实现数据通信。

ViewModel的特点

  • Activity不需要做任何事情,不需要干涉这两个Fragment之间的通信。
  • Fragment不需要互相知道,即使一个消失不可见,另一个也能很好的工作。
  • Fragment有自己的生命周期,它们之间互不干扰,即便你用一个FragmentC替代了B,FragmentA也能正常工作,没有任何问题。

生命周期

ViewModel和SavedInstanceState

ViewModel在Activity的configuration改变过程中可以保持数据不变,但是如果系统把应用杀死掉就没办法了。

如果用户长时间离开应用再进来,应用进程可能已经被系统杀掉了,此时系统会负责恢复之前保存的SavedInstanceState。在onSaveInstanceState()中保存的数据是放在系统进程内存中的,Android系统只允许存放少量的数据,所以最好不要存放一些自定义类的对象进去,可以把自定义对象保存到数据库中,onSaveInstanceState中只保留对象的某个字段比如id,然后再使用ViewModel根据这个id去获取完整的数据。

源码解析

相关推荐阅读