Android-ViewModel+LiveData

在刚接触LifeCycle组件中的ViewModel的时候就有一个疑问,这个ViewModel是不是MVVM中的ViewModel。解决这个疑问首先要了解MVVM是什么。

1. MVVM

MVVM即 Model-View-ViewModel,和MVP一样是一种开发模式,唯一的不同就是MVVM可以实现 View和ViewModel的双向绑定。其实我不太懂这个“双向绑定”,很多人说双向绑定就是view变化可以直接通知model,model变化可以直接通知到view,在MVP模式中也是啊,view变化调用presenter方法改变model,model变化调用presenter的方法改变view。我认为真正的双向绑定是不用写代码去监听就实现的绑定,在Android中这个工具就是dataBinding,这篇我们不讲databinding。

2. ViewModel

所以使用ViewModel并不是单纯的为了MVVM模式,另外一个重要的功能是保持数据。当Activity重建的时候我们通常使用onSaveInstance方法保存数据,但是这个会增加额外的开发工作量,ViewModel自动就有这个功能,当Activity意外被销毁重建的时候,ViewModel中的数据不会丢失。这张是官方给的ViewModel生命周期:

ViewModel生命周期.png

当Activity旋转的时候,activity会被销毁重建,如果我们不做保存数据的处理的话,数据肯定都会被重置,如果存在ViewModel中的话这个出具是可以被保存下来的,这就是ViewModel的主要优势。

3. LiveData

官方说明 LiveData是一个可观察的数据持有类,也就是说是一个被观察者,他有什么好处呢,这是官方列出的几大好处

1. Ensures your UI matches your data state

确保你的UI和你的数据状态匹配,就是说只要监听到数据的变化就可以去更新UI,因为内部处理了生命周期的问题,只要有通知更新UI就一定没有问题。

2. No crashes due to stopped activities

当Activity销毁的时候不会产生崩溃,当我们用普通的MVP模式进行开发的时候,在Activity销毁是通常会手动取消掉网络请求,稍有不慎就可能出现崩溃,但是使用LiveData基本是绝对安全的,只有在LifeCyclerOwner为active(onStart, onResume)的时候才会回调,不用担心网络求情回来的时候界面被销毁导致空指针的问题。

3. Always up to date data

数据会自动更新,上面说只有在active的时候才会回调,但是如果在inactive的时候来了数据怎么办,LiveData可以在LifeCyclerOwner的状态变为active的时候自动将自己持有的data通知给LifeCyclerOwner。

4. Proper configuration changes

当屏幕旋转导致Activity或者Fragment重建时,LivaData可以自动将自己持有的数据更新给UI组件。

4. ViewModel + LiveData会碰撞出怎样的火花

初始状态.png

比如这个例子,第一个TextView使用ViewModel+LiveData,通常两个东西一块使用,第二个是普通数据,就是在Activity里保存数据,第三个是只用也是ViewModel + LiveData,只不过数据源保存到了Activity中,第四个是在Activity中实例化 LivaData

点击右下角的“+”,四个数据一块变化


数据变化.png

当屏幕旋转时:


屏幕旋转.png

可以看出当屏幕旋转时如果不做数据保存的工作普通数据就会丢失,用ViewModel+LiveData (ViewModel保存数据)和 ViewModel+LiveData (Activity保存数据)的数据都又显示了出来,这得益于上面提到的LiveData的第四个优点:当屏幕旋转导致Activity或者Fragment重建时,LivaData可以自动将自己持有的数据更新给UI组件,但是当LiveData在Activity中实例化的时候,这种情况下LiveData本身就被销毁了,所以就不会自动更新。

当再次点击加号的时候:


再次点击.png

只有ViewModel持有的数据继续增加,其他都从1开始了,说明ViewModel确实在屏幕旋转的时候能保存数据。

5. 疑问

5.1 ViewModel是怎么做到保存数据的

解决这个疑问首先从ViewModel的实例化说起

final ViewModelImp viewModel = ViewModelProviders.of(this).get(ViewModelImp.class);

首先看 of这个方法:

 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(activity.getViewModelStore(), factory);
    }

最终生成了一个ViewModelProvider实例,然后调用get方法

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;
    }

这段代码就是首先从mViewModelStore里面拿,如果存在了就直接取出来,否则就生成一个,所以说Activity销毁重建而ViewModel没有销毁的核心就在于这个mViewModelStore没有销毁,而这个mViewModelStore是通过activity.getViewModelStore()拿到的

 public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

这段代码似乎能看出玄机,首先如果Activity的mViewModelStore为null就去NonConfigurationInstances里面拿


NonConfigurationInstances.png

Activity的这个参数是在attache的时候传过来的,我们都知道这个方法是在ActivityThread里的performLaunchActivity的时候调用的


image.png

最终发现这个东西是在startActivityNow的时候调用的。这表明mViewModelStore这个实例的生命周期是由ActivityThread及其上层控制的,所以ViewModel的生命周期比Activity的要长就不难理解了,Activity重建之后AvtivityThread传过来之前保存的ViewModelStore,在配置改变时会调用这个方法保存ViewModelStore:

   @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

5.2 LiveData怎么做到页面重建时恢复数据的

如果是ViewModel保持的LivaData,在Activity的OnCreate中,我们调用了:

 viewModel.mLiveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                Log.d("LiveData", integer.toString());
                textView.setText(String.valueOf(integer));
            }
        });

注册了观察者,当Activity销毁重建时,状态变为active就会调用considerNotify,核心就在这个方法:

private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

首先判断Observer的状态,如果是active就继续,然后再判断一下是否应该是active,这一步应该是为了支持我们重写shouldBeActive方法时做一些自定义的判断,然后下面就是重点了。

实现这个功能的重点是什么,我觉得要区分LifeCycleOwner自然情况下从inactive变为active和异常情况下两种过程,比如activity 在执行onPause onStop了,然后又到了onResume,这个过程是自然的,并且如果没有发生数据变化是不应该去更新数据的,那这个version看着似乎就是为了区分这两种情况的。

当observer 的 mLastVersion >= 当前的version时就不会发出通知observer.mLastVersion只有在通知UI更新时才会调用,而且其值就是mVersion,所以重点就在于mVersion什么时候是>mLastVersion的,mVersion的赋值也只在一个地方:

 protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

当是销毁重建时Observer是一个新的实例,所以其mLastVersion为初始化的-1,而mVersion是LiveData的一个实例变量,没有被初始化,如果之前更新过数据,现在也一定会更新数据;当是正常流程的话mVersion和mLastVersion总是一致的,所以就调不到更新数据。

5.3 LiveData怎么做到页面由inactive变为active自动将数据更新的

这个疑问和第二个类似,当在inactive调用了setValue时,走到dispatchingValue,如果判断当时是inactive,就不会发出通知,当observer重新变为active时,由于mVersion ++ 没有同步给mLastVersion,所以就会发起通知,自动更新UI。

6. 缺点:

当Activity因为内存不足被意外销毁时,ViewModel的数据保存不了,为什么保存不了呢,因为在界面意外被销毁时就回调不了这个方法了onRetainNonConfigurationInstance,导致ViewModelStore没有被保存下来。所以我个人觉得,保存数据还是用onSaveInstance安全一点。

7. 总结

ViewModel + LivaData设计确实很巧妙,尤其是LiveData对数据自动更新的这种机制,还有很多细节这里没有提到,还是需要多多阅读源码,理解透彻。

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

推荐阅读更多精彩内容