kotlin中对于ViewModel的扩展属性viewModelScope理解

用kotlin 搬砖Android 就知道,在Android 中viewModel 中使用协程,建议用viewModelScope。这个玩意需要导入包:

androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2

嗯,在google Android 中 有这么一句话:

ViewModelScope是为 ViewModel您的应用程序中的每个定义的。如果清除,在此范围内启动的任何协程都会自动取消ViewModel 。当您只有在活动时才需要完成工作时,协程非常有用ViewModel。例如,如果您正在计算布局的一些数据,则应将工作范围限制在 ,ViewModel以便在 ViewModel清除 时,工作会自动取消以避免消耗资源。

通俗的理解就是,viewModelScope创建的协程的生命周期和viewModel 的生命周期是绑定的,当viewModel 被取消的时候,viewModelScope创建的协程也会被取消。而我们知道,VIewMoel 的生命周期和Activity或fragment 绑定的。

viewModel 如何取消后台任务

在activity的源码 androidx.activity.ComponentActivity 代码中:

less 复制代码
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
                mReportFullyDrawnExecutor.activityDestroyed();
            }
        }
    });

可以看到,当activity 处于ON_DESTROY的时候,会调用 :

scss 复制代码
if (!isChangingConfigurations()) {
    getViewModelStore().clear();
}

通常来说,界面的配置是不会更改的,更改的时候,比如说,横竖屏切换,比如说 字体,字号,系统主题等等。所以我们就可以简单认为当activity 销毁的时候,就会销毁这个玩意。我们知道 ViewModelStore是用于存放多个viewModel 的,所以他的clear,其实也通过循环调用的VIewModel 中的clear() 函数。

kotlin 复制代码
fun clear() {
    for (vm in map.values) {
        vm.clear()
    }
    map.clear()
}

而fragment 中就稍显复杂,我们这里先不讨论这个玩意。

结合activity 里面调用ViewModel 的clear() 函数的思路,我们就可以发现,无论是JAVA 还是kotlin 需要终止一个后台任务,其中一个办法就是重写ViewModel 中和clear() 相关的函数。但是我们通过阅读源码可以发现:

arduino 复制代码
final void clear() {}

这个函数不可更改,但是他在clear() 函数中调用了:

csharp 复制代码
protected void onCleared() {
}

那么我们想要取消自己的后台任务,那么其中一个思路就是重写onCleared 函数,那么还有没有其他思路呢?我们结合clear的源码:

scss 复制代码
final void clear() {
    mCleared = true;
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    if (mCloseables != null) {
        synchronized (mCloseables) {
            for (Closeable closeable : mCloseables) {
                closeWithRuntimeException(closeable);
            }
        }
    }
    onCleared();
}

可以看到,两个变量mBagOfTags和mCloseables 都调用了closeWithRuntimeException 函数。

typescript 复制代码
private static void closeWithRuntimeException(Object obj) {
    if (obj instanceof Closeable) {
        try {
            ((Closeable) obj).close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

当循环对象是实现与Closeable 的时候,就调用Closeable 的close() 函数。所以我们还有一个思路,就是后台任务实现Closeable 接口,然后为viewModel 添加mBagOfTags或mCloseables。具体添加什么,看业务诉求,结合源码:

swift 复制代码
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
@Nullable
private final Set<Closeable> mCloseables = new LinkedHashSet<>();

我们可以看出mBagOfTags 适合用于要取值得情况,而mCloseables 则通常用于不需要取出来的情况。OK,到这里,我们可以大概知道viewModel 中取消后台任务的几种方式了,那么viewModelScope 想要取消,就一定得通过这几种方式来处理。

viewModelScope 如何取消后台任务

在没有看viewModelScope 的源码 时候,我们可以猜测一下 viewModelScope是如何实现的,这个玩意是变量,而且是一个额外的库实现的,所以,这个这个玩意的实现,一定是扩展属性,对吧。但是扩展属性,得有一个地方存放这个调调,总不能存储为全局变量吧,而恰好,viewModel 中有一个mBagOfTags 支持存取,所以他可能是用到了mBagOfTags。OK,那么我们就来看源码:

kotlin 复制代码
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
​
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }
​
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
​
    override fun close() {
        coroutineContext.cancel()
    }
}

源码超级少,超级单纯好吧。viewModelScope是一个实现Closeable接口的CoroutineScope,而CoroutineScope是协程上下文。我们创建协程的时候,这个玩意是必要的。所以:

kotlin 复制代码
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
​
    override fun close() {
        coroutineContext.cancel()
    }
}

这个的作用其实是取消协程。

kotlin 复制代码
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

重写了viewModelScope的get方法,优先在viewModel 中的mBagOfTags获取,如果没有就创建一个新的,这么就保证了viewModelScope在viewModel 中的唯一性,我们通过viewModelScope 创建的所有协程,都将是viewModelScope的子协程,我们知道当父协程取消的时候,子协程也将被取消,所以viewModelScope其实是利用了这个特性。viewModelScope被添加到mBagOfTags,当viewModel 被取消的时候,会取消mBagOfTags 里面的Closeable。

总结

其实这个代码逻辑是非常简单的,利用了Closeable接口和扩展属性,利用viewModel 取消mBagOfTags的特性实现了这一功能。

相关推荐
雨白7 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹8 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空10 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭10 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日11 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安11 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑12 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟16 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡17 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0017 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体