解答ViewModel使用过程中的疑惑

ViewModel是Google强推的Android开发最佳实践工具包Jetpack中的重要一员,用法比较简单,网上有许多介绍了其用法的文章,故此文不再介绍其用法。本文会从源码角度尝试解答ViewModel的几个相关问题:

  1. ViewModel实例如何创建与存储

  2. 什么时候会清空数据,为何配置发生变化的销毁不会导致数据清空

  3. ViewModel如何避免造成内存泄漏

  4. 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(@NonNull 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(@NonNull String key, @NonNull 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造成内存泄漏的建议和做法:

  1. 避免持有宿主的引用

  2. 在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 {  	@SuppressLint("StaticFieldLeak")  	private Application mApplication;  	  	public AndroidViewModel(@NonNull Application application) {  	mApplication = application;  	}  	  	/**  	* Return the application.  	*/  	@SuppressWarnings("TypeParameterUnusedInFormals")  	@NonNull  	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,就可能会导致内存泄漏。