JetPack的ViewModel的定位是用來存儲管理界面(Activity或Fragment)數(shù)據(jù)的類系羞,ViewModel中的數(shù)據(jù)可以由LiveData進(jìn)行存儲唧领。整個ViewModel的代碼不多救湖,總共就一百多行。主要學(xué)習(xí)一下ViewModel是如何結(jié)合kotlin的協(xié)程的使用,以及ViewModel是如何關(guān)聯(lián)Activity/Fragment的生命周期,在Activity/Fragment的生命周期結(jié)束之后做一些清理工作检访,防止內(nèi)存泄漏。
ViewModel中使用協(xié)程
androidx.lifecycle:lifecycle-viewModel-ktx
在2.1.0之后提供了對ViewModel進(jìn)行了擴(kuò)展仔掸,提供了viewModelScope
的擴(kuò)展屬性脆贵。因此我們可以在ViewModel中進(jìn)行一些協(xié)程操作。
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))
}
擴(kuò)展屬性復(fù)寫了get()方法起暮,主要是先會根據(jù)Key從ViewModel的mBagOfTags(一個Map)
去取相應(yīng)的CoroutineScope卖氨,如果取不到再創(chuàng)建一個CloseableCoroutineScope
并且放到ViewModel的mBagOfTags
中。
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
CloseableCoroutineScope
實現(xiàn)了Closeable接口,并且在close方法中對協(xié)程的Job調(diào)用cancel方法取消協(xié)程的Job筒捺。
如下是ViewModel中相關(guān)的代碼
-
mBagOfTags
是一個HashMap柏腻,我們的viewModelScope屬性就是存儲在這個Map中 - 當(dāng)使用viewModelScope不存在的時候,我們會嘗試創(chuàng)建一個CloseableCoroutineScope的對象系吭,并且放入到
mBagOfTags
這個Map中 -
viewModelScope
是一個CloseableCoroutineScope的對象五嫂,這個類實現(xiàn)了Closeable接口,并且在close方法中對協(xié)程的Job進(jìn)行了cancel - 在ViewModel的clear()方法中會調(diào)用
mBagOfTags
中所有Closeable對象的close()方法
public abstract class ViewModel {
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
//遍歷Map中的所有Closeable對象肯尺,調(diào)用close方法沃缘,防止內(nèi)存泄漏
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;
}
<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);
}
}
}
}
ViewModel關(guān)聯(lián)組件生命周期
在上面ViewModel的源碼中可以看到很多防止內(nèi)存泄漏的操作都是在ViewModel的clear()方法進(jìn)行,順著這個思路可以倒過來溯源则吟,clear()方法是在哪里被調(diào)用了槐臀。往上追溯發(fā)現(xiàn)是在ViewModelStore的clear()方法中調(diào)用了。
// Class to store {@code ViewModels}.
public class ViewModelStore {
private final HashMap<String, ViewModel> 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();
}
}
根據(jù)注釋ViewModelStore這個類是用來存儲ViewModel
的逾滥,ViewModel會被放在HashMap
中峰档。那ViewModelStore的clear()方法又是在哪里調(diào)用呢?這里分析一下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會去監(jiān)聽Lifecycle的生命周期的變化讥巡,在生命周期是ON_DESTROY的時候回去調(diào)用ViewModelStore的的clear()方法。
整個流程如下:
Activity生命周期走到ON_DESTROY -> 調(diào)用ViewModelStore#clear()方法 -> 遍歷ViewModelStore中所有的ViewModel舔哪,調(diào)用ViewModel#clear()方法 -> 調(diào)用ViewModel中的所有Closeable對象的close方法&回調(diào)onCleared()方法欢顷。
tips:平時需要在ViewModel中做一些清除/反注冊的操作的時候可以復(fù)寫onCleared()方法,在這個方法中做操作