Architecture Components源码分析之ViewModel

如果还不清楚什么是ViewModel,可以看下[译] Architecture Components 之 ViewModel 这个系列的文章,翻译自Android Developer的官方文章。

ViewModel 类是被设计用来存储和管理 UI 相关的数据,主要实现了两个功能:

  1. 在配置更改(如:屏幕旋转)时数据可以保留下来。
  2. 在 Fragment 之间共享数据。

接下来会通过分析源码的方式来看看是如何实现这两个功能的。

我们先来找到ViewModel这个类

ViewModel

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

发现只是一个抽象类,并且只有一个空实现的方法,说明实现特殊功能的代码一定在其他地方。
看过官方介绍的应该知道,ViewModel是通过ViewModelProvider创建的:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // 更新 UI
        });
    }
}

那我们从这里开始分析,通过调用ViewModelProviders.of(this).get(MyViewModel.class)是如何获取到ViewModel的那?我们来看ViewModelProvidersof()方法(of方法重载还有Fragment,这里我们只分析Activity,Fragment与activity如出一辙):

    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        initializeFactoryIfNeeded(checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
    }

    @SuppressLint("StaticFieldLeak")
    private static DefaultFactory sDefaultFactory;

    private static void initializeFactoryIfNeeded(Application application) {
        if (sDefaultFactory == null) {
            sDefaultFactory = new DefaultFactory(application);
        }
    }

    private static Application checkApplication(Activity activity) {
        Application application = activity.getApplication();
        if (application == null) {
            throw new IllegalStateException("Your activity/fragment is not yet attached to "
                    + "Application. You can't request ViewModel before onCreate call.");
        }
        return application;
    }

ViewModelProviders.of()方法返回了一个ViewModelProvider对象,该对象需要两个参数:ViewModelStore、Factory。通过命名,我们可以猜测ViewModelStore是一个ViewModel的仓库,用于缓存ViewModel,Factory是工厂类,用于创建ViewModel实例。获取到ViewModelProvider对象后,又调用了它的get方法就获取到ViewModel对象,来看下这个方法:

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

ViewModelProvider类中将构造的参数ViewModelStore和Factory作为成员变量,get方法先是从mViewModelStore.get中获取,如果没有获取到则通过Factory创建一个ViewModel实例,并放入ViewModelStore中,这种使用方式更加验证了上面我们的猜测,之后会仔细分析ViewModelStore以及Factory

既然有一个ViewModel的缓存ViewModelStore,那第一个功能:在配置更改(如:屏幕旋转)时数据可以保留下来,就很好理解了。只要让缓存在Activity配置更改重建是存活下来,那重建后获取的ViewModel就是之前缓存的那个了。

接下的问题便是: ViewModelStore存放在哪里可以保证在Activity配置更改重建是存活下来?

ViewModelStore

字面意思即为ViewModel的仓库

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.get(key);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
        mMap.put(key, viewModel);
    }

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

这个很简单,也很好理解,仅仅是一个HashMap用于存放ViewModel,提供放入,获取,清空的方法。

我们回到ViewModelProviders.of()方法来,这里是通过ViewModelStores.of(activity)获取到的ViewModelStore对象的,我们继续进入这个方法:

import static android.arch.lifecycle.HolderFragment.holderFragmentFor;

public class ViewModelStores {

    private ViewModelStores() {
    }

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

注意到通过静态引入的方法调用了HolderFragmentholderFragmentFor ()方法,接着找到HolderFragment

HolderFragment

public class HolderFragment extends Fragment {

    ...//去除了framgent相关的代码,只保留activity相关

    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

    public static final String HOLDER_TAG =
            "android.arch.lifecycle.state.StateProviderHolderFragment";

    private ViewModelStore mViewModelStore = new ViewModelStore();

    public HolderFragment() {
        setRetainInstance(true);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }

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

    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }


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

    static class HolderFragmentManager {
        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();

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

        private boolean mActivityCallbacksIsAdded = false;

        void holderFragmentCreated(Fragment holderFragment) {
            Fragment parentFragment = holderFragment.getParentFragment();
            if (parentFragment != null) {
                mNotCommittedFragmentHolders.remove(parentFragment);
                parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
                        mParentDestroyedCallback);
            } else {
                mNotCommittedActivityHolders.remove(holderFragment.getActivity());
            }
        }

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

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

这个类就是ViewModel的核心类了,所有的功能都是通过该类来实现的,注意看好啦~

HolderFragment.holderFragment()方法直接返回了sHolderFragmentManager.holderFragmentFor(activity)的结果。而HolderFragmentManagerholderFragmentFor方法实际上就是创建了一个HolderFragment的实例,并添加到参数activity中,为了避免重复添加,先是调用 findHolderFragment(fm) 看看能否找到已添加HolderFragment,如果没有的话再从缓存的Map中找,还是没有话才去创建一个新的实例,放入缓存的Map,并返回这个对象,继而调用getViewModelStore()获取viewModelStore实例。

我们找到了ViewModelStore存放位置,是在HolderFragment中,那它如何保证在Activity配置更改重建是存活下来的?其实关键代码就是Fragment的一个方法:

/**
     * 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>
     */
setRetainInstance(true);

就是这个方法保证了activity因配置更改重建时,该fragment的实例不会销毁,重建后的Activity还是使用该实例。

创建HolderFragment 的过程还有很多细节。

需要注意的是HolderFragmentManager是声明在HolderFragment中的static成员,因此会随着HolderFragment的首次加载创建实例,只存在一个实例并永远在内存中,缓存的map是HolderFragmentManager的成员变量,也会永远在内存中,而HolderFragment可以创建多个实例,所以对于不再需要的HolderFragment实例,需要及时从map中移除。

if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }

这段代码是通过Application的registerActivityLifecycleCallbacks注册一个全局Activity生命周期的回调,任何Activity触发了生命周期都会在mActivityCallbacks中回调对应的方法。HolderFragment的源码中就是通过该回调,在绑定HolderFragment的Activity触发onDestroy方法后移除map中的缓存。

一开始我以为HolderFragmentManager会缓存HolderFragment直到依附的activity销毁才会移除缓存,但后来注意到在HolderFragmentonCreate方法中调用了sHolderFragmentManager.holderFragmentCreated(this);直接移除了缓存。因此,这个缓存仅仅是从HolderFragment的add方法调用到onCreate方法执行为止。或者add了Fragment但是还没有添加到Activity执行onCreate方法,依附的Activity就销毁了,也会回调mActivityCallbacks的onDestroy方法移除HolderFragment的缓存。我想了很久也没有想到这个缓存的使用场景,好像这个缓存是没有意义的。

2017.12.27 补充:
经过JessYan的提点,我意识到HolderFragment的缓存是很有意义的,如果没有这个缓存,在连续调用两次获取ViewModel时,Fragment还没有添加执行onCreate,那这样就会创建两个HolderFragment实例了。并且这种情况在两个Fragment之间发生的话,获取到的就是不同ViewModel实例,也就无法实现Fragment间通讯了。我用as3.0 的Android profiler验证了这种情况,确实创建了两个实例!

Factory

即为创建ViewModel的工厂类,是一个接口,我们可以实现这个接口定义自己的ViewModel工厂。

/**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

上面of()使用的sDefaultFactory默认工厂:

public static class DefaultFactory extends ViewModelProvider.NewInstanceFactory {

        private Application mApplication;

        /**
         * Creates a {@code DefaultFactory}
         *
         * @param application an application to pass in {@link AndroidViewModel}
         */
        public DefaultFactory(@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);
        }
    }

public static class NewInstanceFactory implements Factory {

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

DefaultFactory可以创建AndroidViewModel的对象,调用它AndroidViewModel(@NonNull Application application)构造创建实例,如果不是AndroidViewModel.class则调用父类NewInstanceFactory的create方法调用ViewModel无参数的构造。
如果你的ViewModel实例的创建需要其他参数,则要自己实现Factory复写create。

总结

ViewModelProviders.of()提供ViewModelProviderViewModelProvider通过ViewModelStoreFactory管理和创建ViewModel,ViewModelStore的引用存储在向目标Activity/Fragment中添加的无界面HolderFragment中,并通过setRetainInstance(true);以保证在Activity配置更改重建是存活下来。

关于第二条功能:在 Fragment 之间共享数据也很好理解了,在同一个Activity的不同Fragment种使用ViewModelProviders.of()时,参数需要传入Activity对象,第一次获取ViewModel时会创建一个新对象,而另一个Fragment获取相同ViewModel时,则会从ViewModelStore的缓存中获取,两个Fragment持有的时同一个ViewModel对象,就能实现Fragment之间通讯了。但是这种通讯的前途是必须在同一个Activity中。

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

推荐阅读更多精彩内容