Android 为什么我可以在同一个线程中操作UI和access数据库?
刚刚发现了我对线程理解的差距。我有以下功能,可以将一些结果保存到数据库中并离开活动:Android 为什么我可以在同一个线程中操作UI和access数据库?,android,multithreading,kotlin,Android,Multithreading,Kotlin,刚刚发现了我对线程理解的差距。我有以下功能,可以将一些结果保存到数据库中并离开活动: private fun leave() { GlobalScope.launch { prepareForSaving() println("thread:${Thread.currentThread()}") gameData.save.id = JigsawDatabase(this@GameActivity).save
private fun leave() {
GlobalScope.launch {
prepareForSaving()
println("thread:${Thread.currentThread()}")
gameData.save.id = JigsawDatabase(this@GameActivity).savesDao().upsert(gameData.save).toInt()
val resIntent = Intent()
val res=Gson().toJson(result)
resIntent.putExtra("gameResult",res)
setResult(0, resIntent)
finish()
}
}
从一个侧面看,println
printsthread:thread[DefaultDispatcher-worker-1,5,main]
表示其工作线程。但我仍然可以访问UI,因为finish()
工作正常。完全混乱。UPD: 在将
GlobalScore
更改为我的viewModel的coroutineScope时出现异常:
private fun leave() {
model.viewModelScope.launch {
gameData.save.id =
JigsawDatabase(this@GameActivity).savesDao().upsert(gameData.save).toInt()// throws Cannot access database on the main thread
...
finish()
}
}
即使我将上下文指定为model.viewModelScope.launch(Dispatchers.IO)它仍然可以访问UI,这对我来说毫无意义;DR
Android上有一些硬检查,用于访问不应该在UI线程上执行的内容,但对于不应该在工作线程上执行的内容,没有太多硬检查
解释
这里有几件事。关于协同路由,如果您确实使用了GlobalScope
或任何作用域,而没有指定它将在哪个调度程序上运行,那么它将在dispatcher.Default
上运行,后者是计算线程
GlobalScope.launch {
// This is why you print thread:Thread[DefaultDispatcher-worker-1,5,main] here
println("thread:${Thread.currentThread()}")
}
现在,工作线程确实可以访问/更新您的数据库
gameData.save.id = JigsawDatabase(this@GameActivity).savesDao().upsert(gameData.save).toInt()
正如您所提到的,您也在更新您调用finish()
的UI,并且不会出现崩溃
这是因为并非所有的方法(大多数视图方法实际上不检查此项)都进行检查,以确保它们不是从主线程访问的。只有几件事能证明这一点
以实时数据的setValue
方法为例,说明所做的事情
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
private static void assertMainThread(String methodName) {
if (!ArchTaskExecutor.getInstance().isMainThread()) {
throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ " thread");
}
}
这实际上是显式检查,以确保您在main线程上而不是在worker线程上调用此更新,这是大多数UI元素没有的检查。它们可能会以运行时难以调试的方式失败
调用数据库时获取异常
当您切换到viewModel
范围时,可能已将其设置为Dispatchers.Main
在这一点上,如果您想调用显式进行了检查以确保在工作线程(如房间数据库)上调用的东西,您将遇到此崩溃
Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
finish()
可以从任何线程调用。请注意,使用GlobalScope
是一种代码气味—这主要是针对书籍示例和博客帖子。通常,有一个更好的CoroutineScope
可供使用。我尝试设置其中一个按钮的alpha-也可以。android上是否仍然只有一个UI线程?
是。在iOS、Windows、macOS和Linux上。很少有人尝试使用多线程UI。据我所知,它做得不好,我们到了。您的viewodelScope设置在哪个调度程序上运行?我想我可以用不同的方式来表述这个问题,比如为什么我可以在同一个线程中操作UI和访问数据库?
看起来确实像是安全检查只存在于修改显示列表的方法中。我得到的只是创建视图层次结构的原始线程可以触摸其视图。
添加removeView调用时出现异常。这很有意义。谢谢!这正是我想要的答案。非常感谢。