再学 ViewModel

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 mChildNonConfigs = new HashMap<>();
private final HashMap 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 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)