Android-Jetpack笔记-ViewModel

ViewModel处于数据逻辑层,他的生命周期贯穿整个宿主,如act因屏幕旋转销毁重建时,其依然存活,只有act.finish后,才会自动销毁,因此可以用他来维持宿主的数据状态。现在比较流行的方式是把他当做唯一数据源来驱动UI展示:

1
2
3
view层:          view      (act / fragment)
数据逻辑层: viewModel
数据源: repository (db / network)

另外,还可以通过共享viewModel实现页面间通信,如两个fragment共享act的一个viewModel。

Jetpack笔记代码

本文源码基于SDK 29

使用

引入依赖:

1
2
3
def lifecycle_version = "2.2.0"
//extensions包含Lifecycles、LiveData、ViewModel
implementation "android.arch.lifecycle:extensions:$lifecycle_version"

创建ViewModel

1
2
3
class CommonViewModel extends ViewModel {
public MutableLiveData<String> text = new MutableLiveData<>();
}

在布局文件中使用,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>
<import type="com.holiday.jetpackstudy.viewmodel_livedata.CommonViewModel" />
<variable
name="commonViewModel"
type="CommonViewModel" />
</data>

<TextView
android:id="@+id/tv_text"
android:text="@{commonViewModel.text}"
android:textSize="@dimen/tv_text_size" />
</layout>

在act中使用,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ViewModelActivity extends BaseActivity {
CommonViewModel mCommonViewModel;

void onCreate(Bundle savedInstanceState) {
//传this,基于act创建viewModel
mCommonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
mBinding.setCommonViewModel(mCommonViewModel);
//观察数据变化
mCommonViewModel.text.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
//更新UI
mBinding.tvText.setText(s);
}
});
//旋转屏幕重建act后,viewModel还是同一个实例
QrLog.e(String.valueOf(mCommonViewModel.hashCode()));
}

//点击按钮改变数据
public void changeData(View view) {
mCommonViewModel.text.setValue(String.valueOf(System.currentTimeMillis()));
}
}

在onCreate方法打印ViewModel的hashCode,可见屏幕旋转导致act重建时,mCommonViewModel还是同一个实例,

原理

ViewModelProviders.of(this).get(CommonViewModel.class)为入口,先看ViewModelProviders.of

1
2
3
4
5
6
7
8
9
10
11
12
13
//ViewModelProviders.java
ViewModelProvider of(FragmentActivity activity) {
return of(activity, null);
}

ViewModelProvider of(FragmentActivity activity, Factory factory) {
if (factory == null) {
//工厂类,用来反射创建viewModel
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
//ViewModelStore用来存储ViewModel
return new ViewModelProvider(activity.getViewModelStore(), factory);
}

这里只需知道ViewModelProviders.of得到了当前act的ViewModelProvider,接着看get方法,

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
//ViewModelProvider.java
<T extends ViewModel> T get(Class<T> modelClass) {
//获取类全名
String canonicalName = modelClass.getCanonicalName();
//加上前缀,得到唯一标识key
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

<T extends ViewModel> T get(String key, Class<T> modelClass) {
//获取viewModel
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//如果已经存在,则直接返回
return (T) viewModel;
} else {
//忽略
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
//调用前边提到的工厂类,反射创建viewModel
viewModel = (mFactory).create(modelClass);
}
//存储viewModel
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

看起来代码不是很多,那么viewModel是如何实现act重建后依然存活的呢?

首先viewModel存储在mViewModelStore,而这个store是创建ViewModelProvider时传进来的,即activity.getViewModelStore()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//ComponentActivity.java
ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
//act屏幕旋转重建时,在这里取出ViewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
//act第一次创建时,在这里创建了ViewModelStore
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}

NonConfigurationInstances又是如何存活的呢?

1
2
3
4
5
//Activity.java
Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}

mLastNonConfigurationInstances在attach的时候获取值,

1
2
3
4
//Activity.java
void attach() {
mLastNonConfigurationInstances = lastNonConfigurationInstances;
}

Activity.attachActivityThread.performLaunchActivity中被调用,

1
2
3
4
5
6
7
8
//ActivityThread.java
Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
}

可见来源于ActivityClientRecord.lastNonConfigurationInstances,查找一下可知,r.lastNonConfigurationInstancesperformDestroyActivity时赋值,

1
2
3
4
//ActivityThread.java
performDestroyActivity(){
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
}

这边有点绕,再看到activity,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
//具体实现看下一个方法
Object activity = onRetainNonConfigurationInstance();
onConfigurationInstances nci = new NonConfigurationInstances();
//nci.activity存储的是ComponentActivity里边的NonConfigurationInstances
nci.activity = activity;
return nci;
}

//ComponentActivity.java
public final Object onRetainNonConfigurationInstance() {
NonConfigurationInstances nci = new NonConfigurationInstances();
//这里的NonConfigurationInstances存储了viewModelStore
nci.viewModelStore = viewModelStore;
return nci;
}

至此,简单的概括一下就是,

Activity销毁时,借助ActivityClientRcord来间接保存viewModelStore;Activity重建时,从ActivityClientRcord中间接取出viewModelStore

而所有ActivityClientRcord又被存储在ActivityThread的成员变量里能长期存活:

1
2
//ActivityThread.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

至于act退出时viewModel可以自动销毁,是因为ComponentActivity里添加了一个观察者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//ComponentActivity.java
ComponentActivity() {
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(LifecycleOwner source,Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
//如果是屏幕旋转或语言切换引起的destroy,则无需回调
if (!isChangingConfigurations()) {
//内部会遍历所有ViewModel,回调onCleared
getViewModelStore().clear();
}
}
}
});
}

关于Lifecycles,可以阅读我的早些的笔记。

优缺点

  • 优点:
    • 页面退出时,自动销毁
    • 屏幕旋转、语言切换后数据不丢失,而onSaveInstanceState在面对复杂数据时需要序列化
    • 不持有view层,方便单元测试
  • 缺点:
    • 虽然要比onSaveInstanceState简单,但是viewModel只能在屏幕旋转和语言切换后的页面重建维持数据,当页面意外销毁时数据无法恢复,而这点onSaveInstanceState可以做到,关于viewModel如何实现这一点,可以看我的下一篇笔记。

参考文章