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