Android架构组件之ViewModel源码解析

ViewModel是Google官方MVVM架构的核心组件之一。同时,在Google推出的Android Jetpack组件中,也将ViewModel放在了Architecture类别之中。ViewModel类旨在以一种有生命周期的方式存储和管理与UI相关的数据,并且不会因为屏幕旋转等配置变化后而销毁。


ViewModel生命周期

从ViewModel生命周期图中可以看出我们发现,当activity因屏幕旋转而销毁,但是ViewModel一直存在,正是因为ViewModel有如此的生命周期,所以ViewModel在MVVM中可以作为数据存储区,是连接View和Model重要组件。
本文主要通过分析ViewModel源码,搞清楚如下几个问题:

  • ViewModel是如何创建的?
  • ViewModel是如何存储的,具体放在哪?
  • ViewModel如何实现旋转屏幕而不销毁?

一、ViewModel创建过程

使用过ViewModel的同学都应该知道,当我们想要创建一个ViewModel对象的时候,不是通过new关键字去创建的,而是通过ViewModelProviders.of(this).get(ViewModel.class)这种调用方式创建的,简言之就是通过ViewModelProviders去创建。其实,我们想想也可以知道,ViewModel想要有生命周期,那么它势必就要和我们的Activity或者Fragment相关联起来,至于如何关联,源码会给我们答案。

我们先来看下ViewModelProviders.of()方法,调用这个方法最终会走到下面这2个方法其中之一,一个是在Activity里面调用,另一个则是在Fragment中调用:

    public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
        Application application = checkApplication(checkActivity(fragment));
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }

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

我们看到这个方法最终会返回一个ViewModelProvider对象,在生成这个对象的时候需要传递2个参数,第一个参数是ViewModelStore,第二个参数是Factory。第一参数我们等到讨论ViewModel具体是存储在哪边的时候再说,暂且略过。这边先重点看下第二个参数Factory,从代码上来看,如何我们没有显示地传入Factory对象的话,就会去拿一个默认的Factory对象。事实也就是如此,而且这个默认的Factory对象还是个单例。其实,AndroidViewModelFactory就是ViewModelProvider的一个静态内部类,具体代码如下所示:

    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

        private static AndroidViewModelFactory sInstance;

        @NonNull
        public static AndroidViewModelFactory getInstance(@NonNull Application application) {
            if (sInstance == null) {
                sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }

        private Application mApplication;

        public AndroidViewModelFactory(@NonNull Application application) {
            mApplication = application;
        }

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

至此,我们的ViewModelProvider对象总算是创建完成了,接下来就要调用它的get()方法来具体创建我们所需要的ViewModel对象了,具体如下:

    // ViewModelProvider # get(@NonNull Class<T> modelClass)
    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);
    }

    // ViewModelProvider # get(@NonNull String key, @NonNull Class<T> modelClass)
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

由上述代码我们可以知道,对于我们想要获取的ViewModel对象,首先会根据key从mViewModelStore中去寻找,看看是否已经创建过,如果找到则直接返回。如果之前没有创建过,则调用mFactory的create()方法去创建,如果我们没有显示地传入Factory对象,则调用默认的Factory对象(即AndroidViewModelFactory单例对象)去创建,由AndroidViewModelFactory源码可知它的create()方法会通过反射去创建我们的ViewModel对象。在创建完毕之后,就会把这个ViewModel对象存入mViewModelStore之中,那么很明显这个mViewModelStore内部肯定是一个HashMap结构。至此,我们已经将ViewModel创建过程解析完毕了,接下来看下创建好的ViewModel是存储在什么地方的。

二、ViewModel存储位置

在解析ViewModel创建过程时,我们遗漏了一个点,那就是创建ViewModelProvider对象时传入的第一个参数ViewModelStore,它是通过ViewModelStores.of(activity)或者ViewModelStores.of(fragment)来创建的。其实,创建出来的ViewModelStore对象最后复制给了我们上面所提到的mViewModelStore,具体可以看下如下源码:

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

而我们通过分析ViewModel创建过程,知道了最终创建出来的ViewModel对象,就是存储在mViewModelStore之中的。那么,现在就很明显了,ViewModel的存储位置其实就是mViewModelStore的存储位置,我们需要搞清楚ViewModelStores.of()方法的执行过程就行。

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }

    public static ViewModelStore of(@NonNull Fragment fragment) {
        if (fragment instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) fragment).getViewModelStore();
        }
        return holderFragmentFor(fragment).getViewModelStore();
    }

我们具体看下ViewModelStores.of()方法的执行过程,由于我们的Activity或者Fragment一般不会去实现ViewModelStoreOwner接口,所以基本都是通过holderFragmentFor(xxx).getViewModelStore()这种方式去创建ViewModelStore对象。
那么,我们先来看下这个创建方式的前半段holderFragmentFor(xxx):

    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

    public static HolderFragment holderFragmentFor(Fragment fragment) {
        return sHolderFragmentManager.holderFragmentFor(fragment);
    }

很明显,这个方法返回的是一个HolderFragment对象,而且它继承自Fragment。HolderFragment对象的创建都是通过调用sHolderFragmentManager.holderFragmentFor()方法实现的,那么这个sHolderFragmentManager其实是HolderFragment内部类HolderFragmentManager的对象,我们一起看下它的holderFragmentFor()方法,这里以入参为Activity为例:

    HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity);
            if (holder != null) {
                return holder;
            }

            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            holder = createHolderFragment(fm);
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
    }

    private static HolderFragment findHolderFragment(FragmentManager manager) {
            if (manager.isDestroyed()) {
                throw new IllegalStateException("Can't access ViewModels from onDestroy");
            }

            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
            if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
                throw new IllegalStateException("Unexpected "
                        + "fragment instance was returned by HOLDER_TAG");
            }
            return (HolderFragment) fragmentByTag;
        }

        private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
            HolderFragment holder = new HolderFragment();
            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
            return holder;
        }

    private ActivityLifecycleCallbacks mActivityCallbacks =
                new EmptyActivityLifecycleCallbacks() {
                    @Override
                    public void onActivityDestroyed(Activity activity) {
                        HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
                        if (fragment != null) {
                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + activity);
                        }
                    }
                };

由源码可知,会先去查找Tag为HOLDER_TAG的HolderFragment是否已经创建过,如果已经创建过则直接返回,从这我们可以得到的信息是一个Activity/Fragment只会创建一个HolderFragment。如果没有找到,则会从mNotCommittedActivityHolders缓存中进行查找,同样如果找到就直接返回。如果仍然没有找到,则说明之前未创建过,则调用createHolderFragment()方法创建HolderFragment对象,并在完成创建之后存入mNotCommittedActivityHolders。同时,我们还注意到,这里还注册了Activity生命周期监听,目的是在Activity销毁的时候可以移除mNotCommittedActivityHolders中的对应数据。
之后,在拿到HolderFragment对象之后,通过调用它的getViewModelStore()方法就可以拿到ViewModelStore对象了,它其实就是HolderFragment的成员变量。

    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }

至此,通过以上分析,我们知道通过ViewModelStores.of()方法获取的ViewModelStore对象其实就是HolderFragment的成员变量。那么,也就是说我们创建的ViewModel对象其实也就是保存在所创建出来无UI的HolderFragment之中的。需要注意的一点是,对于同一个Activity/Fragment而言,这种无UI的HolderFragment只会存在一个,但是存在HolderFragment之中的ViewModel可以有多个,即一个Activity/Fragment可以对应多个ViewModel对象。

三、ViewModel旋转屏幕不销毁原理

ViewModel旋转屏幕不销毁的原理其实就在HolderFragment的构造方法之中。

    public HolderFragment() {
        setRetainInstance(true);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }

    /**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated:
     * <ul>
     * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
     * will be, because the fragment is being detached from its current activity).
     * <li> {@link #onCreate(Bundle)} will not be called since the fragment
     * is not being re-created.
     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
     * still be called.
     * </ul>
     */
    public void setRetainInstance(boolean retain) {
        mRetainInstance = retain;
    }

由源码可知,setRetainInstance(boolean) 是Fragment中的一个方法。根据官方注释,我们可以知道,当设置setRetainInstance(true)时,当我们旋转屏幕的时候,Activity会重绘,但是Fragment不会重绘,它将会保留,这也就意味着旋转屏幕时Fragment的onCreate和onDestory方法都不会调用。
无UI的HolderFragment正是运用了这个特性,在其中存放一个专门用于存储ViewModel对象的ViewModelStore(其内部为HashMap),如此ViewModelStore中所有的ViewModel都不会因为屏幕旋转等配置变化后而销毁。

总结

在创建ViewModel对象的过程中,会生成一个无UI的HolderFragment。对于同一个Activity/Fragment,只会有一个HolderFragment,但是可以有多个ViewModel。ViewModel存储在无UI的HolderFragment中,具体以键值对形式存储在ViewModelStore的HashMap中。Activity/Fragment的HolderFragment会保存在全局单例的HolderFragmentManager的HashMap中,在Activity/Fragment销毁时会进行相应的移除。

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