0%

何以ViewModel

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

  1. ViewModel实例如何创建与存储
  2. 什么时候会清空数据
  3. 为何配置发生变化的销毁不会导致数据清空
  4. ViewModel如何避免造成内存泄漏
  5. AndroidViewModel怎么做到不会发生内存泄漏的

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

一切得从获取ViewModel具体实例的方法开始说起。
val viewModel = ViewModelProvider(this).get(TestViewModel::class.java) 为例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}

在构造ViewModelProvider对象实例时,ViewModelProvider会通过入参获取到ViewModel实例的创建者和存储者。接下来看看ViewModel实例是如何被创建及存储的。

1
2
3
4
5
6
7
8
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// androidx.lifecycle.ViewModel
protected void onCleared() {
}

@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();
}

// androidx.lifecycle.ViewModelStore
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}

ViewModel数据清空即被调用onCleared方法,而ViewModel的onCleared方法只会在ViewModelStore#clear中会被调用,阅读源码发现这意味着ViewModelStore的HashMap成员中的ViewModel,而这只会发生在宿主Activity#onDestroy或Fragment被移除时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// androidx.activity.ComponentActivity
getLifecycle().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()) { // notice!!!
getViewModelStore().clear();
}
}
}
});

总结:会在宿主Activity或Fragment被销毁时清空数据
Google官方对于ViewModel生命周期与Activity生命周期的关系说明:

3. 为何配置发生变化的销毁不会导致数据清空

从上面notice!!!标注的代码行中可以发现,只有是非配置变化导致的Activity#onDestroy才会触发ViewModelStore的clear方法!

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

经分析,我们已经知道在ViewModel的宿主Activity或Fragment在被销毁时会调用ViewModel#clear,但假设我们在ViewModel中持有的宿主的引用,而又不在clear时释放引用,那此时便会造成内存泄漏。

一些避免ViewModel造成内存泄漏的建议和做法:

  1. 避免持有宿主的引用
  2. 在ViewModel#clear时释放宿主的引用、取消网络请求、关闭数据库连接,如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class MyViewModel : ViewModel() {
    private var activityRef: WeakReference<Activity>? = null

    fun setActivity(activity: Activity) {
    activityRef = WeakReference(activity)
    }

    fun clear() {
    activityRef?.clear()
    activityRef = null
    }
    }

5. AndroidViewModel怎么做到不会发生内存泄漏的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的Context来避免内存泄漏。这是因为Application Context的生命周期与应用程序的生命周期相同,而Activity的生命周期是短暂的,当Activity被销毁时,它的Context也会被销毁,如果在ViewModel中使用了Activity的Context,就可能会导致内存泄漏。