美文网首页
kotlin中对于ViewModel的扩展属性viewModel

kotlin中对于ViewModel的扩展属性viewModel

作者: BlueSocks | 来源:发表于2023-11-10 18:31 被阅读0次

    前言

    用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 代码中:

        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的时候,会调用 :

    if (!isChangingConfigurations()) {
        getViewModelStore().clear();
    }
    
    

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

    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
    
    

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

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

    final void clear() {}
    
    

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

    protected void onCleared() {
    }
    
    

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

    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 函数。

    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。具体添加什么,看业务诉求,结合源码:

    @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,那么我们就来看源码:

    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是协程上下文。我们创建协程的时候,这个玩意是必要的。所以:

    internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
        override val coroutineContext: CoroutineContext = context
    
        override fun close() {
            coroutineContext.cancel()
        }
    }
    
    

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

    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的特性实现了这一功能。

    相关文章

      网友评论

          本文标题:kotlin中对于ViewModel的扩展属性viewModel

          本文链接:https://www.haomeiwen.com/subject/pnzhwdtx.html