Android:MVVM,ViewModel的工作原理之创建、销毁、保存、复用

前言

这里有个基础知识需要了解一下,AppCompatActivity继承了FragmentActivity继承了ComponentActivity继承了Activity,在这里的讲解我们是以继承AppCompatActivity为基础的情况下说明。

1.ViewModel创建流程

  • mViewModel = ViewModelProviders.of(this).get(vm.class)
  • mViewModel = ViewModelProvider(this).get(vm.class)
    这里第一种方式已经过时废弃了,可以使用第二种,其实第一种方式后面调用还是使用的下面这个方法,这里我们就直接分析第二个方法了。

这里可以看到,初始化过程分为两步,先初始化ViewModelProvider,我们来看一下源码:

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

这里的ViewModelStoreOwner就传入当前的AppCompatActivity或者Fragment就可以了,我们看一下ViewModelStoreOwner源码:

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

他是一个接口,Fragment,FragmentActivity等都实现了这个接口。下面我们来看一下ViewModelStore是什么:

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

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

ViewModelStore里面有一个HashMap,这个HashMap是用来存放ViewModel,还提供了一个clear()方法,这个方法里的操作很简单,就是调用存放ViewModel的onCleared()方法,以及清空HashMap。ViewModel其实很简单,就是个抽象类,里面有个onCleared()方法。

下面我们来看看ViewModelProvider(ViewModelStoreOwner)随后调用了ViewModelProvider(ViewModelStore, Factory)方法,我们来看一下getViewModelStore()的源码,是如何创建ViewModelStore的,这里我们看Activity的:

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

这里我们可以看到,先判断是否需要还原ViewModelStore,这个后面会再讲到;然后就是实例化一个ViewModelStore返回。下面我们来看一下Factory是怎么来的:

owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance()

可以看到,他会判断传入的activity是否有是HasDefaultViewModelProviderFactory,他是一个接口,ComponentActivity、Fragment都实现了这个接口,我们看一下ComponentActivity中getDefaultViewModelProviderFactory()方法:

    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        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 (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

继续看SavedStateViewModelFactory()方法:

    public SavedStateViewModelFactory(@NonNull Application application,
            @NonNull SavedStateRegistryOwner owner,
            @Nullable Bundle defaultArgs) {
        mSavedStateRegistry = owner.getSavedStateRegistry();
        mLifecycle = owner.getLifecycle();
        mDefaultArgs = defaultArgs;
        mApplication = application;
        mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }

可以看到这个Factory就是ViewModelProvider.AndroidViewModelFactory单列

下面我们再来看get(vm.class)方法:

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

继续:

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

这里使用mFactory创建出viewModel,调用就是AndroidViewModelFactory的create()方法:

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }

然后将viewModel存储到ViewModelStore中,到这里ViewModel就创建完成了。

2.ViewModel的生命周期

image.png

这张ViewModel生命周期图时谷歌官方提供的,从这上面显示activity在横竖屏旋转重建时ViewModel也是一直存在内存中。

经过ViewModel创建的源码分析,我们知道ViewModel是存放在ViewModelStore中的,ViewModelStore有clear()方法用来清除存放的ViewModel,我们先来找一下这个ViewModelStore.clear()在哪里调用:

Fragment中销毁
    public void onDestroy() {
        mCalled = true;
        FragmentActivity activity = getActivity();
        boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
        if (mViewModelStore != null && !isChangingConfigurations) {
            mViewModelStore.clear();
        }
    }

这里是在onDestroy()方法中调用的,注意这里有个判断!isChangingConfigurations(),这是横竖屏切换时不会去销毁ViewModel。

FragmentActivity中销毁
    protected void onDestroy() {
        super.onDestroy();

        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }

        mFragments.dispatchDestroy();
    }

这里还是在onDestroy()方法中调用的,且横竖屏切换时不会去销毁ViewModel。

这里可以知道当页面销毁的时候ViewModel也是会被销毁的,但是横竖屏切换不去销毁,那么按照常理来讲不销毁就是想复用,也就是说在Activity横竖屏切换重建页面的时候,ViewModel是不销毁的需要复用,那么我们就来找找他是如何复用的。

FragmentActivity中恢复

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
            ......
        }
        ......
    }

在onCreate的时候会从NonConfigurationInstances中将ViewModelStore恢复出来,这段恢复的代码是不是有点熟悉,在创建的时候getViewModelStore()方法中有一样的代码。同时在这里我们也看到了Fragment保存状态的恢复,这些Fragment保存状态中就包含了ViewModelStore,这里关于Fragment的我们稍后再看,先看Activity的。

现在我们找到了ViewModelStore在重建Activity中恢复的地方了,那就开始寻找是在哪里保存的呢。

FragmentActivity中保存
    /**
     * Retain all appropriate fragment state.  You can NOT
     * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
     * if you want to retain your own state.
     */
    @Override
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

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

在这里我们看到了Activity的ViewModelStore被保存在了NonConfigurationInstances中,之前在恢复的时候我们也看到了是从NonConfigurationInstances中恢复出来的,,同时也找到了Fragment信息的保存代码,关于Fragment的同样是后面再看。这里有个小知识点,从官方注释可以知道,我们如果需要做自定义的数据保存需要重写onRetainCustomNonConfigurationInstance()而不是onRetainNonConfigurationInstance()这点要注意。

剩下的就跟Activity的启动流程有关了,这里不详细说明,就大概说一下。在ActivityThread调用performDestroyActivity()方法的时候会调用retainNonConfigurationInstances()方法将NonConfigurationInstances保存到ActivityClientRecord中,即lastNonConfigurationInstances属性上,然后在ActivityThread 调用performLaunchActivity()方法执行Activity.attach()方法的时候传递给重建的新的Activity。

Fragment保存

下面我们来看一下Fragment中是怎么保存ViewModel的。之前在看Activity的时候,我们就看到了FragmentManagerNonConfig信息保存和恢复的代码,Fragment的相关内容就在这里面。

这里我们使用反向寻找的办法,从Activity中保存的代码中向上寻找会比较简单一点。
还记得在FragmentActivity中onRetainNonConfigurationInstance()中的代码吗:

FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

跟下去,在FragmentController中:

    public FragmentManagerNonConfig retainNestedNonConfig() {
        return mHost.mFragmentManager.retainNonConfig();
    }

这里的mFragmentManager是FragmentManagerImpl,是FragmentManager的现实类。继续,FragmentManager中:

    FragmentManagerNonConfig retainNonConfig() {
        setRetaining(mSavedNonConfig);
        return mSavedNonConfig;
    }

继续下去:

    private static void setRetaining(FragmentManagerNonConfig nonConfig) {
        if (nonConfig == null) {
            return;
        }
        List<Fragment> fragments = nonConfig.getFragments();
        if (fragments != null) {
            for (Fragment fragment : fragments) {
                fragment.mRetaining = true;
            }
        }
        List<FragmentManagerNonConfig> children = nonConfig.getChildNonConfigs();
        if (children != null) {
            for (FragmentManagerNonConfig child : children) {
                setRetaining(child);
            }
        }
    }

这里可以看到,使用递归的方式将mSavedNonConfig其中fragment.mRetaining = true,然后将mSavedNonConfig返回。下面我们来看看mSavedNonConfig是什么,怎么来的:

    // Saved FragmentManagerNonConfig during saveAllState() and cleared in noteStateNotSaved()
    FragmentManagerNonConfig mSavedNonConfig;

这里官方有解释,大概意思就是在saveAllState()保存FragmentManagerNonConfig,并在noteStateNotSaved()中清除。那就很简单了,找saveAllState()方法,这个方法特别长,这里只展示跟saveNonConfig有关的部分代码:

    Parcelable saveAllState() {
        ......
        mStateSaved = true;
        mSavedNonConfig = null;
        ......
        saveNonConfig();
        return fms;
    }

继续看saveNonConfig()方法,这里我们就展示跟ViewModelStore有关的代码了:

    void saveNonConfig() {
        ArrayList<Fragment> fragments = null;
        ArrayList<FragmentManagerNonConfig> childFragments = null;
        ArrayList<ViewModelStore> viewModelStores = null;
        if (mActive != null) {
            for (int i=0; i<mActive.size(); i++) {
                    ......
                    if (viewModelStores == null && f.mViewModelStore != null) {
                        viewModelStores = new ArrayList<>(mActive.size());
                        for (int j = 0; j < i; j++) {
                            viewModelStores.add(null);
                        }
                    }

                    if (viewModelStores != null) {
                        viewModelStores.add(f.mViewModelStore);
                    }
                }
            }
        }
        if (fragments == null && childFragments == null && viewModelStores == null) {
            mSavedNonConfig = null;
        } else {
            mSavedNonConfig = new FragmentManagerNonConfig(fragments, childFragments,
                    viewModelStores);
        }
    }

这里的mActive是一个SparseArray<Fragment>,for循环将其中的所有Fragment的ViewModelStore存放到ArrayList<ViewModelStore> viewModelStores,然后在实例化一个FragmentManagerNonConfig赋值给mSavedNonConfig。那么这条线就通了,Fragment的ViewModel最后是用过FragmentManagerNonConfig交给了NonConfigurationInstances进行保存的。那saveAllState()又是谁来调用的呢,是由FragmentController来管理的,在FragmentActivity调用onSaveInstanceState()方法的时候就会同时保存Fragment的状态。

Fragment恢复

还记得FragmentActivity中的恢复代码吗:

mFragments.restoreAllState(p, nc != null ? nc.fragments : null);

这里从NonConfigurationInstances中拿到了需要恢复的FragmentManagerNonConfig调用了FragmentController的restoreAllState()方法:

    public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
        mHost.mFragmentManager.restoreAllState(state, nonConfig);
    }

继续,这里代码也很多,我们只看ViewModelStore相关的就行:

    void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
        ......
        List<FragmentManagerNonConfig> childNonConfigs = null;
        List<ViewModelStore> viewModelStores = null;
        if (nonConfig != null) {
            List<Fragment> nonConfigFragments = nonConfig.getFragments();
            childNonConfigs = nonConfig.getChildNonConfigs();
            viewModelStores = nonConfig.getViewModelStores();
            ......
        }
        mActive = new SparseArray<>(fms.mActive.length);
        for (int i=0; i<fms.mActive.length; i++) {
            FragmentState fs = fms.mActive[i];
            if (fs != null) {
                ......
                ViewModelStore viewModelStore = null;
                if (viewModelStores != null && i < viewModelStores.size()) {
                    viewModelStore = viewModelStores.get(i);
                }
                Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig,
                        viewModelStore);
                ......
            }
        }
        ......
    }

这里还是比较好理解的,大概就是从FragmentManagerNonConfig取出保存的List<ViewModelStore>,然后循环将ViewModelStore赋值到原先对应的Fragment上。

至此Activity、Fragment创建,销毁,保存,复用ViewModel的流程就基本清楚了。

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