阅读 458

读源码长知识 | 动态扩展类并绑定生命周期的新方式

不使用继承和组合,如何动态地扩展类?比如,如何给 Activity 扩展一个 String 属性,当 Activity 被销毁时,将其置空?

在阅读viewModelScope源码时,发现了一种新的方式。

这是读源码长知识系列的第四篇,该系列的特点是将源码中的设计思想运用到真实项目之中,系列文章目录如下:

  1. 读源码长知识 | 更好的RecyclerView点击监听器

  2. Android自定义控件 | 源码里有宝藏之自动换行控件

  3. Android自定义控件 | 小红点的三种实现(下)

  4. 读源码长知识 | 动态扩展类并绑定生命周期的新方式

协程需隶属于某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 中的对象,让其对扩展更加友好!

本文使用 mdnice 排版