讀源碼長知識 | 動態擴展類並綁定生命週期的新方式

不使用繼承和組合,如何動態地擴展類?好比,如何給 Activity 擴展一個 String 屬性,當 Activity 被銷燬時,將其置空?web

在閱讀viewModelScope源碼時,發現了一種新的方式。算法

這是讀源碼長知識系列的第四篇,該系列的特色是將源碼中的設計思想運用到真實項目之中,系列文章目錄以下:緩存

  1. 讀源碼長知識 | 更好的RecyclerView點擊監聽器編輯器

  2. Android自定義控件 | 源碼裏有寶藏之自動換行控件ide

  3. Android自定義控件 | 小紅點的三種實現(下)post

  4. 讀源碼長知識 | 動態擴展類並綁定生命週期的新方式this

協程需隸屬於某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 排版

相關文章
相關標籤/搜索