时间:2018-11-16 03:02:12

标签: android kotlin android-architecture-components android-viewmodel kotlin-coroutines

我特别关心将用户启动的数据插入本地数据库。

以下模式在将Kotlin协程与[Android Architecture Components] ViewModels结合使用的示例(包括来自JetBrains,Google / Android等官方来源)中很普遍。

class CoroutineScopedViewModel : ViewModel(), CoroutineScope {
    private val _job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + _job

    override fun onCleared() {
        super.onCleared()
        _job.cancel()
    }

    fun thisIsCalledFromTheUI() = launch {
        /* do some UI stuff on the main thread */
        withContext(Dispatchers.IO) {
            try {
                /* do some IO, e.g. inserting into DB */
            } catch (error: IOException) {
                /* do some exception handling */
            }
        }
    }
}

我对documentation的理解是,在上面的示例中,coroutineContext被销毁时,在UI上下文中启动的协程(通过ViewModel定义)将被取消,但是withContext(Dispatchers.IO)块中的代码将运行完毕。

但是,在我从全局范围(启动/异步)协同程序模型(1.0.0之前)重构项目之前,我觉得我需要澄清一些事情:

我对文档的阅读是否正确?还是在withContext(Dispatchers.IO)块运行完成之前销毁视图模型也会触发该作业的取消?即该模型可以用于将数据插入我的数据库,还是在用户回击或导致ViewModel所有者关闭以致最终丢失数据的情况下出现一些奇怪的计时问题?

我不想无意中引入计时错误,因为我误解了一些东西,因此将我的代码转换为类似于上面显示的模型。

编辑:

因此,我决定进行一些测试,在我看来,所有使用此模型写入数据库的示例都可能存在基本错误。

修改代码以记录发生的情况,例如:

class ChildViewModel : ViewModel(), CoroutineScope {
    private val _job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + _job

    override fun onCleared() {
        super.onCleared()
        Log.d("onCleared", "Start")
        _job.cancel()
        Log.d("onCleared", "End")
    }

    fun thisIsCalledFromTheUI() = launch {
        Log.d("thisIsCalledFromTheUI", "Start")
        GlobalScope.launch(Dispatchers.IO) {
            Log.d("GlobalScope", "Start")
            delay(15000)
            Log.d("GlobalScope", "End")
        }
        withContext(Dispatchers.IO) {
            Log.d("withContext", "Start")
            delay(10000)
            Log.d("withContext", "End")
        }
        Log.d("thisIsCalledFromTheUI", "End")
    }
}

如果您让它运行到完成,则会导致以下结果:

D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/withContext: End
D/thisIsCalledFromTheUI: End
D/GlobalScope: End

但是,如果您在withContext结束之前关闭“片段/活动”(不是应用程序),则会得到以下提示:

D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/GlobalScope: End

至少向我表明,您不能使用它来将非瞬态数据写入数据库。

1 个答案:

答案 0 :(得分:1)

  

我对文档的理解是,在以上示例中,在coroutineContext被销毁时,在UI上下文中启动的协程(通过ViewModel定义)将被取消,但是{ {1}}块将运行完毕。

这不是对文档的正确阅读。 withContext(Dispatchers.IO)不会启动另一个协程,它只是在其块的持续时间内更改当前协程的上下文。因此,该协程以及您启动的所有其他协程将被取消,而不会提供与之关联的具有不同工作(或根本没有工作,例如withContext)的新父上下文。

但是,您建议使用GlobalScope进行持久性操作的想法只是针对您正在测试的场景的本地补丁,您仍然无法保证它会完成。用户可以完全退出应用程序,而Android可以终止该过程。

因此,如果您的目标是构建真正可靠的应用程序,则必须适应以下事实:在协程完成之前,不会将任何信息写入DB。希望您在数据库事务中运行该操作,如果您的程序被杀死,该事务将自动回滚,否则将无法防止不一致。