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 )来解决内存泄漏的问题。
ViewModel
和 onSaveInstanceState
ViewModel 是在内存中,因此其读写速度更快,但当进程被系统杀死后,ViewModel 中的数据也不存在了。从数据存储的类型上来看,ViewModel 适合存储相对较重的数据,例如网络请求到的 List 数据,而 onSaveInstanceState
适合存储轻量可序列化的数据。