解答ViewModel使用过程中的疑惑
ViewModel是Google强推的Android开发最佳实践工具包Jetpack中的重要一员,用法比较简单,网上有许多介绍了其用法的文章,故此文不再介绍其用法。本文会从源码角度尝试解答ViewModel的几个相关问题:
ViewModel实例如何创建与存储
什么时候会清空数据,为何配置发生变化的销毁不会导致数据清空
ViewModel如何避免造成内存泄漏
AndroidViewModel怎么做到不会发生内存泄漏的
1. ViewModel实例如何创建与存储
一切得从获取ViewModel具体实例的方法开始说起。
以val viewModel = ViewModelProvider(this).get(TestViewModel::class.java) 为例说明:
|
1 | public class ViewModelProvider { private final Factory mFactory; // 用于构造ViewModel实例 private final ViewModelStore mViewModelStore; // 用于存储ViewModel实例 // 构造ViewModelProvider对象,Activity or Fragment都实现了ViewModelStoreOwner接口和ViewModelProvider.Factory接口 public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; }} |
|
Activity和Fragment都实现了ViewModelStoreOwner和HasDefaultViewModelProviderFactory接口,分别用于创建ViewModelStore实例和ViewModel实例。
|
1 | // Activitypublic class ComponentActivity extends androidx.core.app.ComponentActivity implements ContextAware, LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, OnBackPressedDispatcherOwner, ActivityResultRegistryOwner, ActivityResultCaller// Fragmentpublic class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, ActivityResultCaller |
|
在构造ViewModelProvider对象实例时,ViewModelProvider会通过ViewModelStoreOwner类型入参获取到ViewModel实例的创建者和存储者。接下来看看ViewModel实例是如何被创建及存储的。
|
1 | public <T extends ViewModel> T get( Class<T> modelClass) { // 把传入的类名组成一个key去读取ViewModel实例 String canonicalName = modelClass.getCanonicalName(); if (canonicalName == null) { throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels"); } return get(DEFAULT_KEY + ":" + canonicalName, modelClass); } |
|
|
1 | public <T extends ViewModel> T get( String key, Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); // 会在ViewModelStore的HashMap<String, ViewModel>类型成员mMap中通过组合的key去读取 if (modelClass.isInstance(viewModel)) { if (mFactory instanceof OnRequeryFactory) { ((OnRequeryFactory) mFactory).onRequery(viewModel); } // 实例存在且合理,则返回读取到的ViewModel实例 return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel != null) { // TODO: log a warning. } } if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { // 通过类反射的方式去构造实例 viewModel = mFactory.create(modelClass); } // 存储实例 mViewModelStore.put(key, viewModel); return (T) viewModel; } |
|
总结:在获取ViewModel的时候会先从ViewModelStore中的HashMap数据结构中读取实例,如果ViewModelStore中不存在该实例则通过反射方式创建实例并且存储到ViewModelStore中。
2. 什么时候会清空数据
|
1 | // androidx.lifecycle.ViewModel.ktprotected void onCleared() { } @MainThread final void clear() { mCleared = true; if (mBagOfTags != null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { closeWithRuntimeException(value); } } } onCleared(); }// androidx.lifecycle.ViewModelStore.ktpublic final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } |
|
ViewModel数据清空即被调用onCleared方法,而ViewModel的onCleared方法只会在ViewModelStore#clear中会被调用,阅读源码发现ViewModelStore的clear只会发生在宿主Activity#onDestroy或Fragment被移除时:
|
1 | // androidx.activity.ComponentActivitygetLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { // Clear out the available context mContextAwareHelper.clearAvailableContext(); // And clear the ViewModelStore if (!isChangingConfigurations()) { // 非配置变化引起的destroy getViewModelStore().clear(); } } } }); |
|
总结:ViewModelStore中的map会在宿主Activity或Fragment销毁时被清理。
为何配置发生变化的销毁不会导致数据清空?
从上面注释的代码行中可以发现,只有是非配置变化导致的Activity#onDestroy才会触发ViewModelStore的clear方法。
但有个问题,我们知道尽管是配置变化引起的Activity销毁,但新建的Activity是一个全新的对象,那这个新Activity对象是如何继承前一个Activity中的ViewModel实例的呢?换句话说,或者是如何继承前一个Activity中的ViewModelStore实例的呢?
答案就在Activity的NonConfigurationInstances类型的mLastNonConfigurationInstances成员中。
因配置变化引起的Activity销毁重建属于异常销毁重建,NonConfigurationInstances类型成员的activity会持有之前被异常销毁的activity对象,也就持有了ViewModelStore实例,在每次Activity创建包括重建时都会先从NonConfigurationInstances对象中读取,NonConfigurationInstances对象没有才会创建新的ViewModelStore实例。
3. ViewModel如何避免造成内存泄漏
Google官方对于ViewModel生命周期与Activity生命周期的关系说明:
经分析,我们已经知道在ViewModel的宿主Activity或Fragment在被销毁时会调用ViewModel#clear,但假设我们在ViewModel中持有的宿主的引用,而又不在clear时释放引用,那此时便会造成内存泄漏。
一些避免ViewModel造成内存泄漏的建议和做法:
避免持有宿主的引用
在ViewModel#clear时释放宿主的引用、取消网络请求、关闭数据库连接,如:
|
1 | class MyViewModel : ViewModel() { private var activityRef: WeakReference<Activity>? = null fun setActivity(activity: Activity) { activityRef = WeakReference(activity) } fun clear() { activityRef?.clear() activityRef = null }} |
|
4. AndroidViewModel怎么做到不会发生内存泄漏的
|
1 | public class AndroidViewModel extends ViewModel { private Application mApplication; public AndroidViewModel( Application application) { mApplication = application; } /** * Return the application. */ public <T extends Application> T getApplication() { return (T) mApplication; } } |
|
来看下AndroidViewModel的源码,AndroidViewModel是ViewModel的一个子类,它提供了一个Application对象的引用,通过在AndroidViewModel中使用Application Context而不是Activity或Fragment的Context来避免内存泄漏。这是因为Application Context的生命周期与应用程序的生命周期相同,而Activity的生命周期是短暂的,当Activity被销毁时,它的Context也会被销毁,如果在ViewModel中使用了Activity的Context,就可能会导致内存泄漏。