这篇文章旨在用最简单的方式教会你 ViewModel 如何使用,及它的工作原理。
ViewModel介绍
ViewModel是Google推出的JetPack框架组件的一个,它的主要功能是包装数据,包装后的数据在其宿主(Activity)重建时,数据不会被被重建。
通俗的讲,就是:
在Android系统中,Activity在配置更改后,Activity可能会重启(最常见如屏幕旋转),此时我们更改过的Activity的数据(包括在此Activity中网络请求的数据) 就会被销毁然后重新初始化,这些修改就会丢失。
例如:如果我们不作处理,在EditText中输入内容,旋转屏幕后,Activity会被销毁重启,我们之前输入的内容也会丢失。这当然不是我们希望看到的。
- 传统方法中,我们可以在Activity销毁前使用onSaveInstanceState()的Bundle中存储数据,Activity重启时从onCreate()的Bundle中还原其数据,但此方法仅适用于可以序列化然后反序列化的少量数据,而不适用于潜在的大量数据,如列表数据或位图。
- 现在Jetpack组件中的ViewModel能帮助我们在Activity重建时,保护数据不被销毁。 它也可以用于同一个Activity下的所有Fragment之间的数据共享。 真正实现将数据和UI分离。
官方推荐ViewModel和LiveData联合使用,但是为了大家更清楚的理解ViewModel的职责和使用,本篇文章将ViewModel单独拿出来使用,关于LiveData的使用,将在下篇文章细说。
大家看下这张Activity和ViewModel生命周期对比(图片摘自Android开发者官网):
从图中我们可以看出,因为 ViewModel封装了数据,数据的生命周期比Activity还长,意味着数据不会再随Activity的生命周期而变化,某种程度上实现了UI(Activity )和数据(ViewModel)的分离。
说的有点多了,我们进入正题:
ViewModel使用
基础使用
- 创建新项目,在 MainActivity 的布局文件中给 TextView 添加 Id:data_tv。
- 创建 ModelView 封装的数据类:
public class MyBean extends ViewModel {
private String dataBean;
String getDataBean() {
if (dataBean == null) {
dataBean = “数据”; // 初始化数据
}
return dataBean;
}
void changeValue() {
dataBean = dataBean + “ +1 “;
}
}
- 在 MainActivity 中:
public class MainActivity extends AppCompatActivity {
private static final String*TAG = “MainActivity”;
private MyBean mBean;
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView valueTv = findViewById(R.id.data_tv);
// ViewModel的应用:通过 ViewModelProviders 获取我们自己的 ViewModel(myBean)
mBean = ViewModelProviders.of(this).get(MyBean.class);
mBean.getDataBean();
valueTv.setText(mBean.getDataBean());
valueTv.setOnClickListener(*new*OnClickListener() {
@Override
public void onClick(View v) {
mBean.changeValue();
Log.d(TAG, “onClick: 当前 Value 值: “ + mBean.getDataBean());
}
});
Log.d(TAG, “onCreate: “);
}
@Override
protected void onDestroy() {
Log.d(TAG, “onDestroy: “);
super.onDestroy();
}
}
OJBK,代码已经写完了~
代码很简单:
Activity 初始化的时候初始化我们的 ViewModel(MyBean)并赋值,将ViewModel 的值显示在 TextView 上,然后我们在 TextView 上添加了点击事件,如果点击,触发 changeValue() 修改 ViewModel 的值。
然后我们运行APP,点击三下TextView,然后旋转屏幕,再次点击TextView,同时查看Log:
D/MainActivity: onCreate:
D/MainActivity: onClick: 当前 Value 值: 数据 +1
D/MainActivity: onClick: 当前 Value 值: 数据 +1 +1
D/MainActivity: onClick: 当前 Value 值: 数据 +1 +1 +1
D/MainActivity: onDestroy:
D/MainActivity: onCreate:
D/MainActivity: onClick: 当前 Value 值: 数据 +1 +1 +1 +1
Log显示表示:我们ViewModel的数据确实在屏幕旋转后存活下来了。
恭喜你,ViewModel的精华部分你已经体验过了。
如果你想TextView的值随点击事件改变,可以通过 TextView.setText 方法,但LiveData是更优雅简便的实现,下篇分析。
在Fragment之间分享数据
一个 Activity 中的两个或多个 Fragment 需要相互通信是很常见的。
设想一个常见情况:一个 Activity 中同时显示两个Fragment,一个Fragment 用来显示数据的列表,另一个 Fragment 显示所选数据的内容。
这种情况并不简单,因为两个 Fragment 都需要定义一些接口,并且 Activity 必须将这两个 Fragment 绑定在一起。此外,如果有第三个 Fragment ,这两个Fragment 都必须处理另一个 Fragment 的各种场景。
但是,如果我们采用ViewModel,事情就变简单了:
mBean = ViewModelProviders.of(getActivity()).get(MyBean.class);
两个 Fragment 都通过该方式获取 ViewModel 。这样,当 Fragment 各自会收到相同的 ViewModel 实例,该实例的作用域是此 Activity 的作用域。无论有多少个Fragment都可以通过该方式与Activity和所有Fragment共享数据。
好处:
- 该 Activity 不需要做任何事情,也不需要知道任何有关此通信的信息。
- 除了ModelView 的之外,Fragment 不需要互相了解。如果其中一个Fragment 销毁了了,另一个仍然像往常一样工作。
- 每个 Fragment 都有自己的生命周期,并且不受另一个 Fragment 生命周期的影响。如果一个 Fragment 替换另一个 Fragment,那么UI将继续工作而不会出现任何问题。
其他
单纯的ViewModel的使用并不能帮我们提高开发效率,下面是几种常见的扩展:
-
LiveData: ViewModel对象和LiveData(生命周期观察器)联合使用,这也是Android的官方推荐。ViewModel加上了LiveData之后,在Activity中便可以感知到数据的变化,并在此更新UI。 真正实现UI和数据的分离,但数据变化时,UI仍然可以随之改变。 此外,LiveData 还有更多贴心的设计,这个我们下篇再聊。
-
AndroidviewModel:如果 ViewModel型需要 Context来做一些操作。这个需求是很常见的。此时我们可以使用或扩展AndroidViewModel类,这个比较简单,大家搜索看一下AndroidViewModel的代码即可,此处不再赘述。
-
static扩展:如果 ViewModel 想实现 Activity 之间的数据共享,只需要在ViewModel 类内具体封装的数据类添加 static 声明,例如:
public class MyBean extends ViewModel {
static private String dataBean;
...
}
这意味着全局只有一份 dataBean 实例,但当我们需要共享登陆用户信息的时候,这个比较有用。
到这里,相信你对ViewModel的使用已经有了自己的理解了
如果你对ViewModel的工作原理有兴趣,请继续阅读,我会带着你从源码一步一步完全理解 ViewModel 的工作原理。
如果没有兴趣,可以直接拉到文章末尾看总结~
ViewModel 原理:
ViewModel 的工作原理核心的问题就是:
ViewModel 存储在哪里?
ViewMoedl 如何实现Activity被销毁时而自己不被销毁呢?
- 一步一步来看源码,我们从使用过的这一句入手
mBean = ViewModelProviders.of(this).get(MyBean.class);
点击进入 ViewModelProviders.java ,这是它的方法列表:
可以看到,两个check方法为空判断,然后看 of 方法:
@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(ViewModelStores.of(activity), factory);
}
这个不是很难,可以看出,ViewProviders 主要职责:
- 通过 Application 获取 AndroidViewModelFactory(这个 AndroidViewModelFactory 是个单例)。
- 初始化并返回 ViewModelProvider。需要传入 ViewModelStore 和 AndroidViewModelFactory。Activity持有ViewModelStroe(因为 AppCompatActivity 继承 FragmentActivity,而 FragmentActivity 实现了 ViewModelStoreOwner,拥有ViewModelStroe)所以此处使用 ViewModelStores.of(activity),这个 ViewModelStores .of() 我们下边会看。
- 然后我们进入 ViewModelProvider.java:
public class ViewModelProvider {
private static final String DEFAULT_KEY = "android.arch.lifecycle.ViewModelProvider.DefaultKey";
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
@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;
}
public static class NewInstanceFactory implements Factory {
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class <T> modelClass) {
try{
return modelClass.newInstance();
} catch (InstantiationException*e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
public static class AndroidViewModelFactory extends
ViewModelProvider.NewInstanceFactory{
private static AndroidViewModelFactory sInstance;
@NonNull
public static AndroidViewModelFactory getInstance(@NonNull Application application) {
if ( sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}
private Application mApplication;
public AndroidViewModelFactory(@NonNull Application application) {
mApplication = application;
}
···
}
这个类主要用来取数据,主要就两个方法:
-
构造方法:传入ViewModelStroe,Factory。
Factory:AndroidViewModelFactory,静态内部类,继承NewInstanceFactory,主要用来将 ModelView.Class实例化。 -
get方法:检查ViewModelStore 中是否有指定ViewModel的实例,如果已经有,就从ViewModelStore取,如果没有,就通过Factory实例化。
- 然后我们来看 ViewModelStores ,在这里我们可以找到Activity 重建时ViewModel 不被破坏的原因:
@NonNull
@MainThread
public static ViewModelStore of(@NonNull FragmentActivity activity) {
if(activity instanceof ViewModelStoreOwner) {
return((ViewModelStoreOwner) activity).getViewModelStore();
}
return holderFragmentFor(activity).getViewModelStore();
}
重点在最后一句代码中,传入activity 从 holderFragment中取出 ViewModelStore。
- 我们点进这个HolderFragment.java :
public HolderFragment() {
setRetainInstance(true);
}
我们找到了这个方法,这个方法的含义就是,在Activity因为配置更改而销毁重建时,fragment不会执行 onDestroy() 而被保留。这是 Fragment 的使用方法,我们知道就行,如果还想进入 Fragment一探究竟,这里有个推荐链接。
- 然后我们在 HolderFragment.java 发现了 HolderFragmentManager 这个静态内部类:
static class HolderFragmentManager{
private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();
...
看到这里,我想大家都明白了:HolderFragmentManager并通过键值对的方式存储Activity和holderFragment ,以完成Activity和holderFragment 的映射。
总结:
ViewModel的实现原理:
1. 真正的数据是存储在 ViewModelStroe的,通过 ViewModelProvider 来存取,
2. ViewModelStroe 和 holderFragment 建立了联系,holderFragment 保证了Activity重建时 ViewModelStroe 不被销毁。
我是雷加,如果您喜欢我的文章,请留下你的赞;如有疑问和建议,请在评论区留言
欢迎Follow,Star 我的GitHub,感谢 ~~~
GitHub : https://github.com/QzwJuHao
我的 Jetpack 专栏:
Jetpack系列 - Lifecycle从入门到源码
——————The End