美文网首页
再学 ViewModel

再学 ViewModel

作者: Drew_MyINTYRE | 来源:发表于2022-06-12 20:41 被阅读0次

    ViewModel 能干些啥?

    • ViewModel 可作为 UI 数据的持有者,在 activity/fragment 重建时 ViewModel 中的数据不受影响,同时可以避免内存泄漏。

    • 可以通过 ViewModel 来进行 activity 和 fragment ,fragment 和 fragment 之间的通信,无需关心通信的对方是否存在,使用 application 范围的 ViewModel 可以进行全局通信。

    • 可以代替 Loader,ViewModel 与 Room 和 LiveData 一起使用以替换 Loader。 ViewModel 确保数据在设备配置更改后仍然存在。 当数据库发生更改时,Room 会通知 LiveData ,然后 LiveData 会使用修改后的数据更新 UI。

    我们来思考几个问题?

    • 如何做到 activity 重建后 ViewModel 仍然存在?

    • 如何做到 fragment 重建后 ViewModel 仍然存在?

    • 如何控制作用域?(即保证相同作用域获取的 ViewModel 实例相同)

    • 如何避免内存泄漏?

    首先我们要先了解一下 ViewModel 的结构

    它是一个抽象类,主要有 clear 方法,它是 final 级,不可修改,clear 方法中包含 onClear 钩子,开发者可重写 onClear 方法来自定义数据的清空。

    ViewModelStore:内部维护一个 HashMap 以管理 ViewModel。

    ViewModelStoreOwner: 实现类为 ComponentActivity 和 Fragment,此外还有 FragmentActivity.HostCallbacks。

    ViewModelProvider:用于创建 ViewModel,其构造方法有两个参数,第一个参数传入 ViewModelStoreOwner ,确定了 ViewModelStore 的作用域,第二个参数为 ViewModelProvider.Factory,用于初始化 ViewModel 对象,默认为 getDefaultViewModelProviderFactory() 方法获取的 factory。

    简单来说 ViewModelStoreOwner 持有 ViewModelStore 持有 ViewModel。

    如何做到 activity 重建后 ViewModel 仍然存在?

    ComponentActivity 实现了 ViewModelStoreOwner 接口,意味着需要重写 getViewModelStore() 方法,该方法为 ComponentActivity 的 mViewModelStore 变量赋值。activity 重建后 ViewModel 仍然存在,只要保证 activity 重建后 mViewModelStore 变量值不变即可。

    public ViewModelStore getViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //核心,在该位置重置 mViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
    

    getLastNonConfigurationInstance() 为平台 activity 中的方法

    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
    

    那么我们看一下 mLastNonConfigurationInstances 的赋值位置

    final void attach(NonConfigurationInstances lastNonConfigurationInstances){
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        //...
    }
    

    了解过 activity 的启动流程的小伙伴肯定知道,这个 attach 方法是在 ActivityThread 中的 performLaunchActivity 调用的。

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
        //省略其他参数
        activity.attach(r.lastNonConfigurationInstances);
        r.lastNonConfigurationInstances = null;
        //...
    }
    

    由于 ActivityThread 中的 ActivityClientRecord 不受 activity 重建的影响,所以 activity 重建时 mLastNonConfigurationInstances 能够得到上一次的值,使得 ViewModelStore 值不变 ,所以问题1就解决了。

    如何做到 fragment 重建后 ViewModel 仍然存在?

    fragment 重建后其内部的 getViewModelStore() 方法返回的对象是相同的。

    // Fragment.java
    public ViewModelStore getViewModelStore() {
        return mFragmentManager.getViewModelStore(this);
    }
    

    mFragmentManager(普通 fragment 对应 activity 中的 FragmentManager,子 fragment 则对应父 fragment 的 childFragmentManager)的 getViewModelStore() 方法。

    // FragmentManager.java
    private FragmentManagerViewModel mNonConfig;
    
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        return mNonConfig.getViewModelStore(f);
    }
    

    mNonConfig 竟然是个 ViewModel!

    // FragmentManagerViewModel.java
    private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
    private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
    

    FragmentManagerViewModel 管理着内部的 ViewModelStore 和 child 的 FragmentManagerViewModel 。因此保证 mNonConfig 值不变即能确保 fragment 中的 getViewModelStore() 不变。那么看看 mNonConfig 赋值的位置

    // FragmentManager.java
    void attachController(@NonNull FragmentHostCallback<?> host, @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        //...
        if (parent != null) {
            // 嵌套 fragment 的情况,有父 fragment
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            // host 是 FragmentActivity.HostCallbacks
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
    }
    
    
    // FragmentManagerViewModel.java
    static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
        ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
                FACTORY);
        return viewModelProvider.get(FragmentManagerViewModel.class);
    }
    

    host 是 FragmentActivity.HostCallbacks

    // FragmentActivity.java 内部类
    class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements ViewModelStoreOwner, OnBackPressedDispatcherOwner {
        public ViewModelStore getViewModelStore() {
            // 宿主 activity 的 getViewModelStore
            return FragmentActivity.this.getViewModelStore();
        }
    }
    

    对于嵌套 fragment ,mNonConfig 通过 parent.mFragmentManager.getChildNonConfig(parent) 获取。

    // FragmentManager.java
    private FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
        return mNonConfig.getChildNonConfig(f);
    }
    

    上文提到 FragmentManagerViewModel 管理着 mChildNonConfigs Map,因此子 fragment 重置后其内部的 mNonConfig 对象也是相同的,至此问题 2 就解决了。

    如何控制作用域?

    我们知道 ViewModelStoreOwner 代表着作用域,不同的作用域对应不同的 ViewModelStore ,而 ViewModelStore 内部维护着 ViewModel 的 HashMap ,因此只要保证相同作用域的 ViewModelStore 对象相同就能保证相同作用域获取到相同的 ViewModel 对象,而问题1我们已经解释了重建时如何保证 ViewModelStore 对象不变。因此问题3也解决了。

    如何避免内存泄漏?

    由于 ViewModel 的设计,使得 activity/fragment 依赖它,而 ViewModel 不依赖视图控制器。因此只要不让 ViewModel 持有 context 或 view 的引用,就不会造成内存泄漏。

    总结:

    • activity 重建后 mViewModelStore 通过 ActivityThread 的一系列方法能够保持不变,从而当 activity 重建时 ViewModel 中的数据不受影响。

    • 通过宿主 activity 范围内共享的 FragmentManagerViewModel 来存储 Fragment 的 ViewModelStore 和子 Fragment 的 FragmentManagerViewModel ,而 activity 重建后 FragmentManagerViewModel 中的数据不受影响,因此 fragment 内部的 ViewModel 的数据也不受影响。

    • 通过同一 ViewModelStoreOwner 获取的 ViewModelStore 相同,从而保证同一作用域通过 ViewModelProvider 获取的 ViewModel 对象是相同的。

    • 通过单向依赖(视图控制器持有 ViewModel )来解决内存泄漏的问题。

    ViewModelonSaveInstanceState

    ViewModel 是在内存中,因此其读写速度更快,但当进程被系统杀死后,ViewModel 中的数据也不存在了。从数据存储的类型上来看,ViewModel 适合存储相对较重的数据,例如网络请求到的 List 数据,而 onSaveInstanceState 适合存储轻量可序列化的数据。

    相关文章

      网友评论

          本文标题:再学 ViewModel

          本文链接:https://www.haomeiwen.com/subject/yufnmrtx.html