美文网首页
Android Jetpack ViewModel详解

Android Jetpack ViewModel详解

作者: 雷涛赛文 | 来源:发表于2021-02-28 18:45 被阅读0次

      网上关于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。

相关文章

网友评论

      本文标题:Android Jetpack ViewModel详解

      本文链接:https://www.haomeiwen.com/subject/izygfltx.html