Android-JetpackliveDataAndroid Architecture Components

Android-ViewModel+LiveData

2020-02-24  本文已影响0人  孙大硕

在刚接触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对数据自动更新的这种机制,还有很多细节这里没有提到,还是需要多多阅读源码,理解透彻。

上一篇下一篇

猜你喜欢

热点阅读