一、前言
常规的客户端开发几乎都是数据驱动类型,也就是先获取数据,然后通过UI将数据信息展示给用户。所以数据的管理至关重要。android jetpack另一个重量级组件ViewModel就是为优雅的管理数据而生的。
下面考虑以下场景
1、当页面配置变更(如旋转屏幕),造成页面重建,如何能够更加便捷地恢复数据呢?
2、当数据在activity/fragment/application生命周期内有效,且需支持跨组件共享,如何实现?
答案是:ViewModel。ViewModel是有生命周期感知能力的数据持有共享方案,且能与UI解耦。
那么ViewModel是怎么做到既能感知生命周期,又能保持共享数据,还能和UI解耦呢?
二、工作原理
介绍工作原理之前,先要了解构成ViewModel的关键类
1、ViewModelStore:从命名可以看出,这是ViewModel的仓库。从功能上也是如此,其通过一个HashMap保存ViewModel对象实例。
2、ViewModelStoreOwner:这个接口是ViewModelStore的持有者。实现该接口的主要是ComponentActivity、Fragment组件。
3、ViewModel:这是本文介绍的主角,是一个抽象类。业务方可继承该抽象类并在其内部管理数据。
4、ViewModelProvider:ViewModel对象的构建器,所有的ViewModel对象都需要使用ViewModelProvider构建,切记不可自行new一个ViewModel对象。
5、ViewModelProvider.Factory:ViewModelProvider创建ViewModel对象时依赖的抽象工厂,业务方可以定制或使用默认工厂。
ViewModel框架只有以上这些类,下面看看如何使用ViewModelProvider创建一个ViewModel对象,主要有两种方式:
方式一:
//自定义ViewModel类
public class UserInfoModel extends ViewModel{}
//JetPackActivity.this是是页面类
UserInfoModel userInfoModel = new ViewModelProvider(JetPackActivity.this).get(UserInfoModel.class);
方式二:
//自定义factory,由factory创建viewmodel实例,可以给viewmodel传参
public class UserInfoModelFactory implements ViewModelProvider.Factory {
private final int data;
public UserInfoModelFactory(int data) {
this.data = data;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
if (aClass.isAssignableFrom(UserInfoModel.class)) {
return (T) new UserInfoModel(data);
}
return null;
}
}
//创建factory,并传给ViewModelProvider创建viewmodel实例。
ViewModelProvider.Factory factory = new UserInfoModelFactory(12);
UserInfoModel userInfoModel = new ViewModelProvider(JetPackActivity.this, factory).get(UserInfoModel.class);
说好的原理呢,怎么变成使用了?别着急,下面需要从创建对象为入口,分析ViewModel的创建过程:
1、ViewModelProvider 的构建需要传入一个ViewModelStoreOwner对象。常见的是Activity和Fragment。实际上是使用owner中的ViewModelStore。
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
//使用ViewModelStore
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
2、使用get(@NonNull Class modelClass)方法创建ViewModel实例,实际上是先在ViewModelStore的HashMap中查找实例是否存在,如果存在直接返回,如不存在,则使用factory创建一个并存入ViewModelStore中。
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
//获取类的全限定名称
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
//通过全限定名称,查询并创建viewmodel对象,存储在Map中也是使用的该KEY
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//按照key从ViewModelStore中查找
ViewModel viewModel = mViewModelStore.get(key);
//如果找到且类型一致,则直接返回。所以同一个owner中,viewmodel实例只有一个
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//通过factory创建viewmodel实例,并存入ViewModelStore中
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
通过上述源码知道
1、videmodel实现数据共享,实际上就是确保同一个owner中只有一个实例。因为唯一,所以数据相同且可以共享。
2、videmodel存储在owner持有的ViewModelStore中,所以当owner销毁时,便能通知videmodel执行onCleared()。
3、videmodel的低耦合主要是源自Lifecycle能力,当生命周期销毁时,会通过LifeCycleRegistry触发Videmodel的onCleared()。生命周期如下图。
三、ViewModel应用
了解了viewmodel的工作原理,使用起来就很简单了。下面将创建一个activity和两个fragment,在activity和fragment中创建同一类viewmodel并且传入同样的owner(activity),实现viewmodel数据共享。
首先创建Activity类和fragment类
public class JetPackActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jetpack);
TextView mShowTv = findViewById(R.id.activity_text);
UserInfoModel userInfoModel = new ViewModelProvider(this).get(UserInfoModel.class);
mShowTv.setText(userInfoModel.getValue() + "");
}
}
public class SecondFragment extends Fragment {
public ComponentActivity activity;
private UserInfoModel userInfoModel;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof ComponentActivity) {
activity = (ComponentActivity) context;
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View containView = View.inflate(activity, R.layout.fragment_layout_test, null);
initView(containView);
return containView;
}
private void initView(View rootView) {
TextView mShowTv = rootView.findViewById(R.id.fragment_text);
userInfoModel = new ViewModelProvider(activity).get(UserInfoModel.class);
mShowTv.setText(userInfoModel.getValue()+"");
}
}
以下是activity布局代码activity_jetpack.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/activity_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginTop="25dp"
android:layout_marginRight="25dp"
android:gravity="center"
android:textSize="17dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/activity_text"
android:orientation="horizontal">
<fragment
android:id="@+id/my_first_fragment"
android:name=".SecondFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/my_second_fragment"
android:name=".SecondFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</RelativeLayout>
接下来是fragment的布局fragment_layout_test.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="以下是一个fragment"
android:layout_marginLeft="10dp"
android:textSize="15dp" />
<TextView
android:id="@+id/fragment_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"
android:layout_marginTop="75dp"
android:gravity="center"
android:textSize="17dp" />
</RelativeLayout>
最后创建ViewModel类
public class UserInfoModel extends ViewModel {
private int data = 5;
public UserInfoModel() {
}
public UserInfoModel(int value) {
setValue(value);
}
public void setValue(int value) {
this.data = value;
}
public int getValue() {
return data;
}
@Override
protected void onCleared() {
super.onCleared();
}
}
运行上述代码便可以实现activity和两个fragment中的数据一致了,并且旋转屏幕后数据可以保持。当彻底销毁activity时会调用到ViewModel的onCleared方法。
至此ViewModel的原理和使用便分析结束了,但是demo还只是很简单的数据共享。下期介绍LiveData后将会持续完善demo,实现真正的工程级数据共享能力。