Leo's Studio.

Jetpack中的ViewModel

字数统计: 1.8k阅读时长: 8 min
2019/01/05 Share

前言

首先祝大家在新的一年中,身体健康,心想事成。

算了下,去年技术类博客写了 15 篇,看下数量还算比较满意,可是基本都是前半年写的,那时候刚刚下定决心要好好写博客,后半年因为工作上还有自己偷懒心理,基本都没怎么写了,真是惭愧。

不过经过去年写了这些文章,慢慢也学习到了一些写技术文章上的技巧,希望今年能方得始终,提升文章的质量。

关于 Jetpack

相信已经有不少人对 Google 推出的 Jetpack 系列组件都有所耳闻,现在网上已经有不少的分析文章了,涵盖了用法和源码解析,所以我就不重复造轮子,在这个系列文章中,不会涉及使用教程等,只写一些个人的使用体会,也当作给自己做笔记。

关于本文

这篇文章主要讲 Jetpack 中的 ViewModel

ViewModel

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way.

简而言之,就是在生命周期中管理数据。说到生命周期,我们都知道 Android 中 ActivityFragment 都有各自对应的生命周期,比如 Activity,它的生命周期如下图:

activity_lifecycle

通常我们会在 onCreate() 初始化数据,在 onResume() 中展示数据,onDestroy() 中释放数据,类似下面这些伪代码:

1
2
3
4
5
6
7
8
9
10
11
onCreate() {
initData();
}

onResume() {
displayData()
}

onDestory() {
recycleData();
}

如果没有在 onDestory() 中及时释放某些资源,可能还会导致内存泄漏,这是第一个问题。

第二个问题,Android 系统可能会在内存不足的情况下,回收了 Activity,导致 Activity 重建时数据会丢失,对于这种情况,Android 提供了 onSaveInstanceState 中保存数据,在 onRestoreInstanceStateonCreate 中获取。类似下面这些伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
onSaveInstanceState(Bundle outState) {
outState.putData();
}

onRestoreInstanceState(Bundle outState) {
outState.getData();
}

onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
savedInstanceState.getData();
}
}

这种方式除了重复的胶水代码以外,还存在 Bundle 存储只适用于支持序列化(Serializable 和 Parcelable)的少量数据,当然除了使用 Android SDK 提供的这种方案以外,我们也可以自己实现类似的方案,类似以下伪代码:

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
// Data Repository
Map<String,Data> map;

getData(String id) {
if (map.get(id) == null){
Data data = createData();
map.put(id,data);
}
return map.get(id);
}

removeData(String id) {
map.removeByKey(id);
}

// Activity
onCreate() {
getData(this);
}

onDestory() {
if (!isChangingConfigurations){
removeData(this);
}
}

看了上面解决思路后,我们再来看看 Google 提供的 ViewModel,它是如何解决上面提到的两个问题的。首先看下,ViewModel 的典型用法,来源于官方文档:

首先定义一个 MyViewModel 继承于 ViewModel,这里使用 LiveData 作为数据源,这里我们只需要知道 LiveDataRxJava 是差不多的东西就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<User>>();
loadUsers();
}
return users;
}

private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}

定义好 ViewModel 之后,我们在 Activity 中使用它:

1
2
3
4
5
6
7
8
9
10
11
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.

MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}

我们先看下 ViewModelProviders.of 的函数签名:

1
2
3
4
5
public static ViewModelProvider of(@NonNull Fragment fragment);
public static ViewModelProvider of(@NonNull FragmentActivity activity);
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory);
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory);

需要将当前 ActivityFragment 作为参数,这也是 ViewModel 将数据与生命周期结合起来的地方。那具体也是如何实现的呢?

1
2
3
4
5
6
7
8
9
10
@NonNull                                                                             
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}

factory 顾名思义就是 ViewModel 的工厂类,这里默认使用 AndroidViewModelFactory 最后返回 ViewModelProvider 实例

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
39
40
41
42
// ViewModelProvider
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);

if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}

viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}

// AndroidViewModelFactory
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
return super.create(modelClass);
}

我们将以上两段代码合起来看,其实逻辑是比较清晰的:

ViewModelProvider#create

Factor 的实现可以通过反射来实现,比如默认的 AndroidViewModelFactory 会优先调用使用 Application 作为参数的构造方法,来创建实例。所以,如果自定义的 ViewModel 构造方法有其他参数,就需要自定义 Factor

ViewModelStore 则是 Activity 重建时还能拥有之前数据的保障。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private final HashMap<String, ViewModel> mMap = new HashMap<>();                  

final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}

final ViewModel get(String key) {
return mMap.get(key);
}

/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}

ViewModelStore 的源码很短,可以看到其实就是使用 HashMap 作为数据载体。既然有使用,那就需要清理操作,可以看到有个 clear 它会清除 HashMap 中的缓存数据。我们首先看下 ViewModelProvider 中的 mViewModelStore 是在哪里赋值的:

1
2
3
4
5
6
7
8
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { 
this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}

通过 ViewModelStoreOwner.getViewModelStore 获取 ViewModelStore 实例对象,而 ViewModelStoreOwner 实际就是我们调用 ViewModelProviders.of 中传递的 FragmentActivityFragment

FragmentActivity 中会通过 onRetainNonConfigurationInstancegetLastNonConfigurationInstance 去保持 Activity 因为屏幕旋转等配置发生改变而导致重建时,数据的唯一性

FragmentFragmentActivity 会在 onDestory 时判断是否需要调用 ViewModelStore.clear

1
2
3
4
5
6
7
8
9
10
@Override                                                            
protected void onDestroy() {
super.onDestroy();

if (mViewModelStore != null && !isChangingConfigurations()) {
mViewModelStore.clear();
}

mFragments.dispatchDestroy();
}

讲完 ViewModelStore 的缓存功能之后,我们再来看下,ViewModelProvider.of 不同签名的方法:

1
2
3
4
5
6
7
8
9
10
11
@NonNull                                                         
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
return of(fragment, null);
}

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}

这里需要注意的是,ViewModel 在不同作用域下的实例,首先,FragmentActivityFragment 都实现了 ViewModelStoreOwner 即它们都有各自的 ViewModelStore 简单来说,如果想在 Fragment 中获取依附的 ActivityViewModel 实例,那需要使用 of(FragmentActivity) 的方法,这也是一种 ActivityFragment 之间通信的好方式。

总结

现在我们来比较下自定义实现的方案和 ViewModel ,可以发现其实核心思想是共通,首先我们需要一个保存于 ActivityFragment 生命周期之外的存储空间,在 ViewModel 中是 ViewModelStore,其次我们需要在 ActivityFragment 对应的生命周期中,去初始化和清理这个 ViewModelStore

CATALOG
  1. 1. 前言
  2. 2. 关于 Jetpack
  3. 3. 关于本文
  4. 4. ViewModel
  5. 5. 总结