0
点赞
收藏
分享

微信扫一扫

【协程】ViewModelScope源码解析

前言

使用协程,相信很多同学已经信手拈来了,但是关于​​ViewModelScope​​,可能很多同学在用,但却不知道原理,今天来一探究竟。

​ViewModelScope​​​,顾名思义,在​​ViewModel​​​中使用的协程。
它是​​​ViewModel​​的扩展属性。

推荐理由:

  • 自动取消,不会造成​​内存泄漏​​​,如果是​​CoroutineScope​​​,就需要在​​onCleared()​​方法中手动取消了,否则可能会造成内存泄漏。
  • 配合​​ViewModel​​,能减少样板代码,提高效率。

后面会重点介绍ViewModelScope是怎么做到不会​​内存泄漏​​的。

使用

引入

  • 协程:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
  • viewmodel-ktx:
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")

ViewModelScope虽然是协程,但属于​​androidx.lifecycle​​​包中ViewModel的​​扩展属性​​。

示例:

class MyViewModel :ViewModel() {

fun getData(){
viewModelScope.launch {
// do
}
}

}

使用非常简单,关键在于它是怎么保证不会内存泄露的?

源码分析

来看​​viewModelScope​​源码:

public 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)
)
}

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context

override fun close() {
coroutineContext.cancel()
}
}

先看​​get()​​方法:

get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() +
Dispatchers.Main.immediate)
)
}

return中通过​​setTagIfAbsent​​​创建了协程,并且指定​​主线程​​。

先忽略setTagIfAbsent,来看协程创建的方式:

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context

override fun close() {
coroutineContext.cancel()
}
}

​CloseableCoroutineScope​​,顾名思义,可以关闭的协程。

实现​​Closeable​​​接口,并重写唯一方法​​close()​​​,并在方法中​​取消​​了协程。

现在我们已经知道了​​viewModelScope​​是可以取消的了,关键就在于取消时机的控制了。

回过头在再看setTagIfAbsent,setTagIfAbsent是​​ViewModel​​中的方法

public abstract class ViewModel { 
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;

@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}

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

@SuppressWarnings("unchecked")
<T> 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;
}

@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> 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);
}
}
}
}

在setTagIfAbsent中,以​​HashMap​​的形式把协程对象保存起来了,并配有getTag方法。

可能有同学已经注意到最后的方法​​closeWithRuntimeException​​​,因为这个方法中调用了Closeable接口的close()方法,而close()方法就是用来​​取消协程​​的。

而closeWithRuntimeException方法是谁调用的呢,主要是ViewModel中的​​clear()​​方法。

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

这里是循环保存协程的HashMap,然后调用closeWithRuntimeException取消协程。

那这个ViewModel中的​​clear()​​方法又是谁调用的呢?

查看源码,只有一处调用,就是在​​ViewModelStore​​中

public class ViewModelStore {

private final HashMap<String, ViewModel> mMap = new HashMap<>();

final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}

final ViewModel get(String key) {
return mMap.get(key);
}

Set<String> keys() {
return new HashSet<>(mMap.keySet());
}

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

ViewModelStore的源码比较少,也很简单。

同样也是以HashMap的形式来保存ViewModel。

那是什么时候保存的呢,我们来追踪一下​​put​​方法:

public class ViewModelProvider {

//...

@SuppressWarnings("unchecked")
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//...

mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

//...
}

在​​ViewModelProvider​​的get方法中调用了put,也就是说,我们在创建ViewModel的时候并把其保存了起来。

回过头来再看ViewModelStore,同样也有一个​​clear()​​​方法,同样循环调用​​vm.clear()​​。

继续追踪ViewModelStore的clear()方法是在哪调用的。

是在​​ComponentActivity.java​​中调用的:

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();
}
}
}
});

先是获取​​Lifecycle​​​,并添加​​生命周期​​监听。

在生命周期为​​onDestroy​​的时候,获取ViewModelStore,并调用其clear()方法。

至此,相信大部分同学都明白了ViewModelScope为什么​​不会造成内存泄露​​了,因为在onDestroy的时候会取消执行,只不过这部分工作源码已经替我们完成了。

关于怎么获取到当前生命周期状态的,就涉及到​​Lifecycle​​​相关的知识了,简而言之,不管是​​Activity​​​还是​​Fragment​​​,都是​​LifecycleOwner​​​,其实是父类实现的,比如ComponentActivity。
在父类中通过​​​ReportFragment​​​或​​ActivityLifecycleCallbacks​​​接口来派发当前生命周期状态,具体使用哪种派发方式要看​​Api​​等级是否在29(10.0)及以上,及 则后者。

author:yechaoa

总结

最后,我们再来总结一下​​ViewModelScope​​的整个流程。

  1. 首先在创建ViewModel的时候,会通过ViewModelStore以HashMap的形式把ViewModel保存起来;
  2. 随后我们在调用viewModelScope的时候,会在ViewModel中以HashMap的形式把协程也保存起来,而这个协程实现了Closeable接口,并在Closeable接口的close()方法中取消协程;
  3. 在ViewModel中有个clear()方法,会循环调用close()方法取消协程;
  4. 在ViewModelStore中也有个clear()方法,会循环调用ViewModel中的clear()方法;
  5. 而ViewModelStore中的clear()方法,是由Lifecycle在生命周期执行到onDestroy的时候调用的。

为避免有的同学没理解,我们再反推梳理一次

  1. 在生命周期执行到onDestroy的时候,调用ViewModelStore中的clear()方法;
  2. 在ViewModelStore中的clear()方法中,循环调用ViewModel的clear()方法;
  3. 在ViewModel的clear()方法中,循环调用close()取消协程。

ok,以上就是​​ViewModelScope​​的使用,以及源码分析。

最后

写作不易,如果对你有一丢丢帮助或启发,感谢​​点赞​​支持 ^ - ^


举报

相关推荐

0 条评论