Jetpack全家桶手册之ViewModel 源码分析

前言:

分析源码时我们可以不计较细枝末节,只分析主要的逻辑即可。因此我们来思考几个问题,并从源码中寻找答案

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

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

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

  • 如何避免内存泄漏?

维持我们一贯的风格,我们先来大胆地猜一猜

对于问题1 :activity 有着 saveInstanceState 机制,因此可能通过该机制来处理(事实证明不是

对于问题2:可能 fragment 通过 宿主 activity 或 父 fragment 的帮助来确保 ViewModel 实例在重建后仍然存在

对于问题3:实现一个类似单例的效果,相同作用域获取的对象是相同的

对于问题4:避免 ViewModel 持有 view 或 context 的引用

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

扫描二维码关注公众号,回复: 14714268 查看本文章

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

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

ViewModelStoreOwner :接口, ViewModelStore 的作用域,实现类为 ComponentActivity和 Fragment ,此外还有 FragmentActivity.HostCallbacks

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

简单来说 ViewModelStoreOwner 持有 ViewModelStore 持有 ViewModel

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

在 【背上Jetpack】绝不丢失的状态 androidx SaveState ViewModel-SaveState 分析 中我们提到了androidx.core.app.ComponentActivity 的引入并探讨了其作为中间层的作用

我们已经讲过 SavedStateRegistryOwner 和 OnBackPressedDispatcherOwner 这两种角色,而今天我们来聊一下

ViewModelStoreOwner` 和 `HasDefaultViewModelProviderFactory` 。其中前者代表着
`ViewModelStore` 的作用域,后者来标记 `ViewModelStoreOwner` 拥有默认的
`ViewModelProvider.Factory

那么 ViewModel 的逻辑肯定就在该类了

ComponentActivity 实现了 ViewModelStoreOwner 接口,意味着需要重写getViewModelStore() 方法,该方法为 ComponentActivity 的 mViewModelStore 变量赋值。

activity 重建后 ViewModel 仍然存在,只要保证 activity 重建后 mViewModelStore 变量值不变即

顺着这个思路,我们来看一下 getViewModelStore() 的实现

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

mViewModelStore 的值由 getLastNonConfigurationInstance() 返回的NonConfigurationInstances 对象中的 viewModelStore 赋值,如果此时还为空才去 newViewModelStore 对象。因此我们只需找到

getLastNonConfigurationInstance 中的 NonConfigurationInstances 在哪里保存的即可

getLastNonConfigurationInstance` 为平台 activity 中的方法,返回
`mLastNonConfigurationInstances.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

就解决了

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

对于问题2,有了上面的思路我们可以认定 fragment 重建后其内部的 getViewModelStore() 方法返回的对象是相同的。

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

可以看到 getViewModelStore() 内部调用的是 mFragmentManager (普通fragment 对应 activity 中的 FragmentManager ,子 fragment 则对应父 fragment 的 childFragmentManager )的getViewModelStore() 方法

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

而 FragmentManager 中的 getViewModelStore 使用的是 mNonConfig ,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);
}

我们先看 fragment 的直接宿主是 activity (即没有嵌套)的情况,mNonConfig 由FragmentManagerViewModel.getInstance(viewModelStore) 赋值,而 getInstance 中使用的是ViewModelProvider 获取 ViewModel ,根据我们上面的分析,只要保证作用域(viewModelStore)相同,即可获取相同的 ViewModel 实例,因此我们需要看一下 host 的getViewModelStore 方法。经过一番寻找,host 是 FragmentActivity.HostCallbacks

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

host 的 getViewModelStore 方法返回的是宿主 activity 的 getViewModelStore() ,而 activity 重建后其内部的mViewModelStore 是不变的,因此即使 activity 重建,其内部的 FragmentManager 对象变化,但 FragmentManager 内部的 FragmentManagerViewModel 的实例( mNonConfig )不变,mNonConfig.getViewModelStore 不变,fragment 的 getViewModelStore() 亦不变,fragment 重建后其内部的 ViewModel 仍然存在对于嵌套 fragment ,mNonConfig 通过 parent.mFragmentManager.getChildNonConfig(parent) 获取

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

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

3. 如何控制作用域?

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

因此问题3也解决了。

4. 如何避免内存泄漏?

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

总结

简单的总结一下:

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

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

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

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

更多关于Jetpack全家桶的知识点可以点击这里

猜你喜欢

转载自blog.csdn.net/m0_70748845/article/details/129468153