不使用继承和组合,如何动态地扩展类?比如,如何给 Activity 扩展一个 String 属性,当 Activity 被销毁时,将其置空?
在阅读viewModelScope源码时,发现了一种新的方式。
协程需隶属于某 CoroutineScope ,以实现structured-concurrency
,而 CoroutineScope 应该和某个生命周期组件相绑定,以便同步生命周期。
和 ViewModel 生命周期绑定的viewModelScope
被定义成它的扩展属性。它是怎么做到和 ViewModel 生命周期绑定的:
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 中的对象,让其对扩展更加友好!