JetPack的ViewModel的定位是用来存储管理界面(Activity或Fragment)数据的类,ViewModel中的数据可以由LiveData进行存储。整个ViewModel的代码不多,总共就一百多行。主要学习一下ViewModel是如何结合kotlin的协程的使用,以及ViewModel是如何关联Activity/Fragment的生命周期,在Activity/Fragment的生命周期结束之后做一些清理工作,防止内存泄漏。
ViewModel中使用协程
androidx.lifecycle:lifecycle-viewModel-ktx
在2.1.0之后提供了对ViewModel进行了扩展,提供了viewModelScope
的扩展属性。因此我们可以在ViewModel中进行一些协程操作。
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
扩展属性复写了get()方法,主要是先会根据Key从ViewModel的mBagOfTags(一个Map)
去取相应的CoroutineScope,如果取不到再创建一个CloseableCoroutineScope
并且放到ViewModel的mBagOfTags
中。
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
CloseableCoroutineScope
实现了Closeable接口,并且在close方法中对协程的Job调用cancel方法取消协程的Job。
如下是ViewModel中相关的代码
-
mBagOfTags
是一个HashMap,我们的viewModelScope属性就是存储在这个Map中 - 当使用viewModelScope不存在的时候,我们会尝试创建一个CloseableCoroutineScope的对象,并且放入到
mBagOfTags
这个Map中 -
viewModelScope
是一个CloseableCoroutineScope的对象,这个类实现了Closeable接口,并且在close方法中对协程的Job进行了cancel - 在ViewModel的clear()方法中会调用
mBagOfTags
中所有Closeable对象的close()方法
public abstract class ViewModel {
private final Map mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
//遍历Map中的所有Closeable对象,调用close方法,防止内存泄漏
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();
}
@SuppressWarnings("unchecked")
T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
T getTag(String key) {
if (mBagOfTags == null) {
return null;
}
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
ViewModel关联组件生命周期
在上面ViewModel的源码中可以看到很多防止内存泄漏的操作都是在ViewModel的clear()方法进行,顺着这个思路可以倒过来溯源,clear()方法是在哪里被调用了。往上追溯发现是在ViewModelStore的clear()方法中调用了。
// Class to store {@code ViewModels}.
public class ViewModelStore {
private final HashMap mMap = new HashMap<>();
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
根据注释ViewModelStore这个类是用来存储ViewModel
的,ViewModel会被放在HashMap
中。那ViewModelStore的clear()方法又是在哪里调用呢?这里分析一下Activity的情况。
public ComponentActivity() {
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
在androidx中的ComponentActivity会去监听Lifecycle的生命周期的变化,在生命周期是ON_DESTROY的时候回去调用ViewModelStore的的clear()方法。
整个流程如下:
Activity生命周期走到ON_DESTROY -> 调用ViewModelStore#clear()方法 -> 遍历ViewModelStore中所有的ViewModel,调用ViewModel#clear()方法 -> 调用ViewModel中的所有Closeable对象的close方法&回调onCleared()方法。
tips:平时需要在ViewModel中做一些清除/反注册的操作的时候可以复写onCleared()方法,在这个方法中做操作