网上关于DataBinding,ViewModel,LiveData文章很多,最近结合源码及相关实例分析了一下,本文结合ViewModel的使用来对ViewModel创建进行源码分析。
关于DataBinding的使用,可以参考之前的文章:
Android JetPack DataBinding分析
关于LiveData的使用,可以参考之前的文章:
Android Jetpack LiveData原理分析
一.什么是ViewModel
ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据,让数据可在发生屏幕旋转等配置更改后继续留存。在对应的作用域内,确保只生产出对应的唯一实例。ViewModel一般要配合 LiveData、DataBinding一起使用。
二.MVVM实例
结合MVVM实例来分析一下ViewModel在MVVM中起了什么作用,LiveData是如何与UI进行绑定及UI如何更新。
a.View
public class ViewModelLiveDataFragment extends BaseFragment {
private MainViewModel mMainViewModel;
@Override
public int getLayoutId() {
return R.layout.livedata_layout;
}
@Override
public void initData(View view) {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LivedataLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.livedata_layout, container, false);
View view = binding.getRoot();
mMainViewModel = new ViewModelProvider(this.getViewModelStore(), new ViewModelProvider.NewInstanceFactory()).get(MainViewModel.class);
binding.setViewModel(mMainViewModel);
//通过以下逻辑来保证MutableLiveData变化时来更新UI
//该方法中最终会调用到observe()方法
binding.setLifecycleOwner(this);
return view;
}
}
以上可以看到,每次执行onCreateView()时,都是去new一个ViewModelProvider(),然后通过来get()传入类来获取MainViewModel,是不是每次都会创建一个MainViewModel,那如何确保数据被存储了呢?后面源码会讲到。
b.ViewModel
public class MainViewModel extends ViewModel {
private ImageDepository mImageDepository;
public MainViewModel() {
mImageDepository = new ImageDepository();
step.setValue(1);
getImage(step.getValue());
}
public MutableLiveData<Integer> step = new MutableLiveData<>();
public MutableLiveData<String> imageUrl = new MutableLiveData<>();
public MutableLiveData<String> imageDescription = new MutableLiveData<>();
public void onClick(View view) {
if (view.getId() == R.id.up) {
step.setValue(step.getValue() - 1);
} else if (view.getId() == R.id.down) {
step.setValue(step.getValue() + 1);
}
getImage(step.getValue());
}
private void getImage(int step) {
Observable<ImageBean> observable = mImageDepository.getImage("js", step, 1);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ImageBean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ImageBean imageBean) {
List<ImageBean.ImagesBean> imagesBeans = imageBean.getImages();
ImageBean.ImagesBean imagesBean = imagesBeans.get(0);
String url = ImageBean.ImagesBean.BASE_URL + imagesBean.getUrl();
String des = imagesBean.getCopyright();
imageUrl.setValue(url);
imageDescription.setValue(des);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
}
以上可以看到,MainViewModel内部定义了许多LiveData变量,如果MainViewModel是唯一的,那么LiveData也就被存储下来,当UI从后台处于前台时,可以将最新值同步更新到UI。
c.M
public class ImageDepository {
private RetrofitApi mRetrofitApi;
public ImageDepository() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://cn.bing.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
mRetrofitApi = retrofit.create(RetrofitApi.class);
}
public Observable<ImageBean> getImage(String format, int idx, int n) {
return mRetrofitApi.getImage(format, idx, n);
}
}
三.ViewModel的使用及源码分析
通过以上实例可以看到,MainViewModel继承了ViewModel,然后在View创建时,去获取MainViewModel实例,代码如下:
mMainViewModel = new ViewModelProvider(this.getViewModelStore(), new ViewModelProvider.NewInstanceFactory())
.get(MainViewModel.class);
通过ViewModelProvider.get()来获取MainViewModel实例,看一下内部实现:
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
通过以上可以看到,在去创建ViewModel时,先去会传入两个参数,一个是ViewModelStore,一个是Factory,且两个参数都不能为null;我们在实现中分别传入的是this.getViewModelStore()赋值mViewModelStore,ViewModelProvider.NewInstanceFactory()赋值mFactory,接下来看一下get()方法:
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
....
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
get()方法传入的是ViewModel子类的class,接着获取class名字然后进行拼接作为下个get()方法的key,这个key是唯一的(除非两个class内部是相同的),然后看一下两个参数的get()方法内部逻辑实现:
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
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.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
先从传入的ViewModelStore内部去获取,如果获取到,会返回;如果获取不到,则会通过传入的Factory对象进行创建,创建完后存入mViewModelStore内部,看一下create()逻辑:
public static class NewInstanceFactory implements Factory {
private static NewInstanceFactory sInstance;
@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
return modelClass.newInstance();
}......
}
}
create()方法就是通过反射newInstance()来创建一个实例对象,接下来看一下ViewModelStore的get()方法:
public class ViewModelStore {
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);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
声明了一个HashMap来存储ViewModel,put()进行存储,get(key)进行获取,clear()方法是当Fragement被销毁时才会调用,如果是配置发生变化是不会调用的。
接下来看一下Fragment内部获取getViewModelStore()的实现:
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
调用到FragmentManager的getViewModelStore()方法:
private FragmentManagerViewModel mNonConfig;
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
最终从FragmentManagerViewModel里面去通过getViewModelStore()来获取:
final class FragmentManagerViewModel extends ViewModel {
......
......
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
......
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
......
从内部的HashMap变量mViewModelStores内部去取对应Fragment的ViewModelStore。
简单总结一下:在MVVM使用中,V(Fragment)去获取对应的VM,涉及到的类主要有:
ViewModelStoreOwner:是一个接口,用来获取一个ViewModelStore对象,Fragment实现了该接口;
ViewModelStore:存储多个ViewModel,一个ViewModelStore的拥有者( Frament )配置改变重建时,依然会有这个实例;
ViewModel:一个对 Activity、Fragment 的数据管理类,通常配合LiveData使用;
ViewModelProvider:创建一个 ViewModel 的实例,并且会将ViewModel实例存储在给定的ViewModelStoreOwner中;
调用关系如下:
Fragment-->FragmentManager-->FragmentManagerViewModel-->mViewModelStores-->ViewModelStore-->ViewModel。每个Fragment对应的ViewModelStore是唯一的,通过ViewModelStore的get()来获取对应类唯一的ViewModel、由于key是唯一的,确保获取的值也是唯一的;从而配置[不是销毁]发生变化后,会存储相应信息,即:ViewModel是唯一的,数据是保存下来的。
四.自定义Factory实现
通过以上可以看到,我们在通过ViewModelProvider()去获取ViewModel实例时,会传入两个参数,一个是ViewModelStore,前面已经进行了分析;另外一个是Factory,默认的是传入ViewModelProvider.NewInstanceFactory(),通过NewInstanceFactory的create()方法内部通过反射来创建ViewModel实例。
有个问题,如果我们的ViewModel的构造参数需要传入参数呢?应该如何创建呢?看一下Factory这个接口:
public interface Factory {
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
内部就一个create()方法,需要进行实现,那么上述问题就可以通过本地自己创建一个类来实现Factory,然后在create()内部创建含参数的ViewModel实例就可以了,实现方式如下:
public static class LocalFactory implements ViewModelProvider.Factory {
int step;
String url;
public LocalFactory (int s, String ul) {
step = s;
url = ul;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MainViewModel(step, url);
}
}
public MainViewModel(int s, String t) {
step.setValue(s);
url.setValue(t);
}
mMainViewModel = new ViewModelProvider(this.getViewModelStore(), new MainViewModel.LocalFactory(1,"www.baidu.com")).get(MainViewModel.class);
通过NewInstanceFactory()的create()创建的是无参的对象,如果有参数的需要自定义实现。
五.总结
本文主要通过一个MVVM实例来介绍ViewModel是如何使用的,然后对ViewModel的创建进行了相应的源码分析,验证了ViewModel的唯一性及可存储功能,接下来解释了如何自定义实现创建含参的ViewModel。