Android Jetpack ViewModel详解

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容