不使用繼承和組合,如何動態地擴展類?好比,如何給 Activity 擴展一個 String 屬性,當 Activity 被銷燬時,將其置空?web
在閱讀viewModelScope
源碼時,發現了一種新的方式。算法
這是讀源碼長知識系列的第四篇,該系列的特色是將源碼中的設計思想運用到真實項目之中,系列文章目錄以下:緩存
協程需隸屬於某CoroutineScope
,以實現structured-concurrency
,而CoroutineScope
應該和某個生命週期組件相綁定,以便同步生命週期。url
和ViewModel
生命週期綁定的viewModelScope
被定義成它的擴展屬性。它是怎麼作到和ViewModel
生命週期綁定的:spa
val ViewModel.viewModelScope: CoroutineScope
get() { // 嘗試根據 tag 獲取 CoroutineScope val scope: CoroutineScope? = this.getTag(JOB_KEY) // 命中則直接返回 if (scope != null) { return scope } // 若未命中則構建 CloseableCoroutineScope 並將其和 JOB_KEY 綁定 return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main)) } 複製代碼
這和緩存的寫法一摸同樣。猜想CoroutineScope
實例可能緩存在ViewModel
的某個屬性中,去ViewModel
源碼中確認一下:設計
public abstract class ViewModel {
// 存放 Object對象 的 map private final Map<String, Object> mBagOfTags = new HashMap<>(); // 取值方法 <T> T getTag(String key) { synchronized (mBagOfTags) { return (T) mBagOfTags.get(key); } } // 設置方法 <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; } // ViewModel 生命週期結束時釋放資源 final void clear() { mCleared = true; // 遍歷 map 清理其中的對象 if (mBagOfTags != null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { // 清理單個對象 closeWithRuntimeException(value); } } } onCleared(); } // 清理實現了 Closeable 接口的對象 private static void closeWithRuntimeException(Object obj) { if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (IOException e) { throw new RuntimeException(e); } } } } 複製代碼
ViewModel
預留了後門,是存放Object
對象的HashMap
結構。這使得不修改ViewModel
源碼,就能爲其動態擴展屬性。
ViewModel
在生命週期結束時,會清理後門中全部的Closeable
對象。當擴展屬性也是該類型時類,其生命週期自動和ViewModel
同步。
Cloaseable
接口定義以下:
public interface Closeable extends AutoCloseable {
// 定義如何釋放資源 public void close() throws IOException; } 複製代碼
回到擴展屬性viewModelScope
的獲取算法,從 Map 中獲取viewModelScope
失敗後,會構建CloseableCoroutineScope
對象,它實現了Closeable
接口:
// 可自動取消的 CoroutineScope
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context // 將協程取消 override fun close() { coroutineContext.cancel() } } 複製代碼
設計類的時候,也能夠借用這個套路,預留一個存放Closeable
接口的 Map 屬性,公開取值和設置方法,而且在類生命週期結束時清理 map 中的對象,讓其對擴展更加友好!
本文使用 mdnice 排版