Fragment嵌套Fragment通过ViewModel进行数据同步的操作

一个场景加上源码的分析

1.场景

一个常见的场景,Fragment中有一个TabView和一个ViewPager构成的界面,Tab上展示ViewPager中Rv的某个数量(例如消息,未读的消息数量),消息的数量接口是在外层的Fragment中请求的,但是当我们在内层的Fragment中点击消息条目时需要修改这个数量,以往的做法是通过事件总线发个通知然后更新等等操作。现在我们既然使用有ViewModel,那个在让内层的Fragment获取外层的ViewModel,然后修改数量后postValue就可以,但是实际的效果和预想不相符,并没有引起数据的刷新。

2.代码分析

修改之前的代码:

    private fun handleMessageRead(index: Int) {
        mAdapter?.getItem(index)?.isRead = AppConstant.YES
        mAdapter?.notifyItemChanged(index)
        if (mModel?.type == StatusConfigs.MessageStatus.MESSAGE) {
            if (parentFragmentModel == null) {
                parentFragmentModel = getFragmentViewModel(MessageModel::class.java)
            }
            val value = parentFragmentModel!!.unReadCount.value
            //在这里获取到value是null
            value?.apply {
                this.unreadCount -= 1
                parentFragmentModel?.unReadCount?.postValue(this)
            }
        }
    }

获取到的value是null,不应该啊,网络请求应该是已经给赋值了。最终看了一下两个parentFragmentModel,发现在两个fragment中获取到的是两个不同的对象。
修改后台的代码:

    private fun handleMessageRead(index: Int) {
        if (mAdapter?.getItem(index)?.isRead == AppConstant.NO) {
            mAdapter?.getItem(index)?.isRead = AppConstant.YES
            mAdapter?.notifyItemChanged(index)
            if (mModel?.type == StatusConfigs.MessageStatus.MESSAGE) {
                if (parentFragmentModel == null) {
                    parentFragmentModel = ViewModelProvider(
                        requireActivity(),
                        ViewModelProvider.NewInstanceFactory()
                    ).get(MessageModel::class.java)
                }
                val value = parentFragmentModel!!.unReadCount.value
                value?.apply {
                    value.unreadCount -=1
                    parentFragmentModel?.unReadCount?.postValue(this)
                }
            }
        }
    }

这两个区别就是getFragmentViewModel(MessageModel::class.java)是封装在Base里的一个方法来获取对应的ViewModel:

    protected open fun <T : ViewModel?> getFragmentViewModel(modelClass: Class<T>): T {
        if (mFragmentProvider == null) {
            mFragmentProvider = ViewModelProvider(this)
        }
        return mFragmentProvider!!.get(modelClass)
    }

两个不同就是在构建ViewModelProvider是一个传入的是当前的Fragment一个是activity,那么它们为什么会引起不一样的结果?

3.源码分析

   public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

这里的owner就是我们传入的activity或者fragment,分别看一下它们的getViewModelStore的实现:
activity:

    @NonNull
    @Override
    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;
    }

fragment:

    @NonNull
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore == null) {
            viewModelStore = new ViewModelStore();
            mViewModelStores.put(f.mWho, viewModelStore);
        }
        return viewModelStore;
    }

两者的差别不大,都是如果当前为null的话构建一个ViewModelStore返回,如果有的话直接返回,所以这个ViewModelStore是跟随不同的Fragment和Activity生命周期的,并且所属对象是不同的。
接下来我们再看一下获取ViewModel的get方法:

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

   @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //通过key在ViewModelStore中查找对应的ViewModel
        ViewModel viewModel = mViewModelStore.get(key);
        //判断获取到的viewModel是不是modelClass的实例对象,如果是的话返回当前的viewModel
        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.
            }
        }
        //执行到这说明对应的ViewModel没有找到,那么根据工厂不同来生成ViewModel
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        //存储以及返回对应的viewModel
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

到这里我们也就能分析明白为什么在Fragment中传入this和activity的结果不同的原因了,这是因为生成的ViewModel所属的作用域是不同的,this的话只在当前的Fragment中生效,那么FragmentA和FragmentB分别去创建ViewModel它们是不同的,而如果传入的Activity,那么这个ViewModel的作用域是Activity级别的,所有依附于当前Activity中的Fragment通过activity这个owner获取同一个class的ViewModel都会是同一个。