This is the second day of my participation in the More text Challenge. For more details, see more text Challenge
About Kotlin coroutines, ViewModel related knowledge, in this article will not do the introduction.
To get into this, what is a ViewModelScope?
Add rely on Tips before use ViewModelScope needs: implementation ‘androidx. Lifecycle: lifecycle – viewmodel – KTX: 2.3.0’
packageandroidx.lifecycle ... Ellipsis reversal...private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
/** * CoroutineScope * [CoroutineScope] tied to this [ViewModel]. * When you call [viewModel.oncleared], * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called * * This scope is bound to * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] */
public val ViewModel.viewModelScope: CoroutineScope
get() {
// First try to get a CoroutineScope object with JOB_KEY tag from the cache
// Returns viewModelScope if it hits
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if(scope ! =null) {
return scope
}
// If the cache is not hit, it is added to the ViewModel via setTagIfAbsent()
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
)
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close(a) {
coroutineContext.cancel()
}
}
Copy the code
This tells us that the viewModelScope object is an extended property of the ViewModel. And we know from the comments in the viewModelScope that the scope will be cleared when the ViewModel is cleared, which is called [viewModel.onCleared].
Here’s the question:
- When is the ViewModel cleared?
- Why the viewModelScope is canceled when [viewModel.onCleared] is called?
With the problem to see the source twice the result with half the effort!
Regarding the first question “When is the ViewModel cleared?” This will be covered in a future article on ViewModel
Let’s look at the second question “Why is the viewModelScope cleared when called [viewModel.onCleared]?”
Just look at the onCleared method for the ViewMode.
public abstract class ViewModel {
// Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
/** * This method is called when the ViewModel is no longer used and is destroyed. * This method will be called when this ViewModel is no longer used and will be destroyed. * * It is useful to prevent the ViewModel from leaking when the ViewModel observes some data and needs to clear the subscription to the ViewModel. * It is useful when ViewModel observes some data and you need to clear this subscription to * prevent a leak of this ViewModel. */
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
if(mBagOfTags ! =null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
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) {
thrownew RuntimeException(e); }}}}Copy the code
The onCleared() method is empty. “This scope will be cleared when the ViewModel is cleared, which is called [viewModel.oncleared].” ? How do I cancel without code? The ViewModel source code shows that onCleared() is called by clear(), so let’s see what clear() does:
@MainThread
final void clear() {
// Mark that the current ViewModel has been cleared
mCleared = true;
/ / determine whether mBagOfTags is empty, no is null, the value of the ergodic map and invoke the closeWithRuntimeException () method
if(mBagOfTags ! =null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
Copy the code
Let’s see what mBagOfTags does:
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
Copy the code
It’s a Map to find out when to call mbagoftags.put () :
/** * sets the tags and keys associated with this viewModel. * Sets a tag associated with this viewModel and a key. * If the given newValue is Closeable, it will be closed once clear (). * If the given {@code newValue} is {@link Closeable},
* it will be closed once {@link#clear()}. * <p> * If a value has been set for the given key, this call does nothing and returns the currently associated value, * If a value was already set for the given key, this calls do nothing and * returns currently associated value, the given {@codeNewValue} would be ignored * <p> * If ViewModel has been cleared, then close () will be called on the returned object if it implements closeable. The same object may receive multiple close calls, so methods should be idempotent. * If the ViewModel was already cleared then close() would be called on the returned object if * it implements {@link Closeable}. The same object may receive multiple close calls, so method
* should be idempotent.
*/
@SuppressWarnings("unchecked")
<T> T setTagIfAbsent(String key, T newValue) {
/ / value
T previous;
synchronized (mBagOfTags) {
// Try to obtain whether the key is hit
previous = (T) mBagOfTags.get(key);
if (previous == null) {
// If no, put it in mBagOfTagsmBagOfTags.put(key, newValue); }}// Return value assignment
T result = previous == null ? newValue : previous;
// If this viewModel is marked clear
if (mCleared) {
// We may call close () on the same object multiple times,
// But the Closeable interface requires the close method to be idempotent: "If the stream is closed, then calling this method has no effect."
// It is possible that we'll call close() multiple times on the same object, but
// Closeable interface requires close method to be idempotent:
// "if the stream is already closed then invoking this method has no effect." (c)
// Call Closeable's close method
closeWithRuntimeException(result);
}
return result;
}
Copy the code
The setTagIfAbsent() method is familiar? Turns out it was called when we got the viewModelScope object. We know that the viewModelScope object is cached in the mBagOfTags object of type Map.
The setTagIfAbsent annotation states that T newValue implements the Closeable interface. Let’s review how viewModelScope is fetched:
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
/** * CoroutineScope * [CoroutineScope] tied to this [ViewModel]. * When you call [viewModel.oncleared], * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called * * This scope is bound to * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] */
public val ViewModel.viewModelScope: CoroutineScope
get() {
// First try to get a CoroutineScope object with JOB_KEY tag from the cache
// Returns viewModelScope if it hits
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if(scope ! =null) {
return scope
}
// If the cache is not hit, it is added to the ViewModel via setTagIfAbsent()
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
)
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close(a) {
coroutineContext.cancel()
}
}
Copy the code
The first time viewModelScope is called, the setTagIfAbsent(JOB_KEY, CloseableCoroutineScope) is called for caching. Look at the CloseableCoroutineScope class, which implements the Closeable interface and canceles the coroutineContext object in close().
So far, we know how the viewModelScope object is added to the ViewModel and the viewModelScope is cancelled when the ViewModel is cleared.
The second question is why the viewModelScope is cancelled when called [viewModel.onCleared]. Solve!