簡介
- 當在ViewModel中引入協程,如果直接使用CoroutineScope,那麼需要在onCleared()方法中取消協程,如果忘記取消協程那麼會導致出現內存泄漏等各種問題,此時需要使用ViewModel擴展屬性viewModelScope來實現協程作用域。
viewModelScope源碼分析
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
// 默認使用Dispatchers.Main,方便Activity和Fragment更新UI
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
// 爲了ViewModel能夠取消協程,需要實現Closeable接口
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
ViewModel如何創建以及取消CorutineScope
abstract class ViewModel
// ViewModel通過HashMap存儲CoroutineScope對象
private final Map<String, Object> mBagOfTags = new HashMap<>();
// ViewModel被銷燬時內部執行clear()方法,
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// 取消viewModelScope作用域的協程
closeWithRuntimeException(value);
}
}
}
onCleared();
}
// 取消CoroutineScope
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 如果JOB_KEY已存在且對應的協程作用域不爲空則返回對象,否則創建新的協程作用域,並設置JOB_KEY
@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;
}
// 通過JOB_KEY從HashMap中返回相應的協程作用域
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T getTag(String key) {
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
}