Android MVVM ViewModel 引入協程
ViewModel 引入協程
當我們在 ViewModel 裡面引入協程,首先要在 ViewModel 中新建一個CoroutineScope, 用來管理所有協程任務,同時需要onCleared()
方法裡面取消協程任務,模板程式碼實現如下:
class MyViewModel : ViewModel() { private val viewModelJob = SupervisorJob() private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) override fun onCleared() { super.onCleared() viewModelJob.cancel() // Cancel all coroutines } fun launchDataLoad() { uiScope.launch { sortList() // Modify UI } } suspend fun sortList() = withContext(Dispatchers.Default) { // Heavy work } }
然而,很多情況我們會經常忘記取消協程,導致出現記憶體洩漏等各種問題。那怎麼處理比較好?使用 ViewModel 擴充套件屬性viewModelScope
來優化程式碼。
viewModelScope 方式
AndroidX Lifecycle v2.1.0 在 ViewModel 中引入viewModelScope
,當 ViewModel 被銷燬時它會自動取消協程任務,這個特性真的好用。viewModelScope
管理協程的方式與我們在 ViewModel 引入協程的方式一樣,程式碼實現如下:
class MyViewModel : ViewModel() { fun launchDataLoad() { viewModelScope.launch { sortList() // Modify UI } } suspend fun sortList() = withContext(Dispatchers.Default) { // Heavy work } }
注意 lifecycle-viewmodel-ktx 版本號: 2.1.0-beta01
viewModelScope 內部實現
// androidx.lifecycle.ViewModel.viewModelScope 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)) } internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context override fun close() { coroutineContext.cancel() } }
-
viewModelScope
預設使用Dispatchers.Main
, 方便 Activity 和 Fragment 更新 UI - 注意使用 SupervisorJob 而不是用 Job
- ViewModel 取消協程,需要實現 Closeable 介面
ViewModel 內部取消協程
ViewModel 類通過 HashMap 儲存 CoroutineScope 物件。當使用getTag(JOB_KEY)
方法獲取物件不存在時,建立一個新的 CoroutineScope 並呼叫setTagIfAbsent(JOB_KEY, scope)
方法儲存新建的 CoroutineScope 物件。當 ViewModel 被銷燬時內部會執行clear()
方法。在clear()
方法中遍歷呼叫closeWithRuntimeException
取消了viewModelScope
的協程,實現流程非常清晰。相關程式碼如下:
// androidx.lifecycle.ViewModel private final Map<String, Object> mBagOfTags = new HashMap<>(); <T> T getTag(String key) { //noinspection unchecked synchronized (mBagOfTags) { return (T) mBagOfTags.get(key); } } <T> T setTagIfAbsent(String key, T newValue) { T previous; synchronized (mBagOfTags) { //noinspection unchecked previous = (T) mBagOfTags.get(key); if (previous == null) { mBagOfTags.put(key, newValue); } } T result = previous == null ? newValue : previous; if (mCleared) { closeWithRuntimeException(result); } return result; } @MainThread final void clear() { mCleared = true; if (mBagOfTags != null) { for (Object value : mBagOfTags.values()) { closeWithRuntimeException(value); } } onCleared(); } private static void closeWithRuntimeException(Object obj) { if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (IOException e) { throw new RuntimeException(e); } } }
總結
如果你也正在使用 MVVM 和協程,非常推薦在 ViewModel 中使用viewModelScope
方式。不僅簡化 ViewModel 程式碼,而且還能管理協程生命週期。