不使用继承和组合,如何动态地扩展类?好比,如何给 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 排版