我的应用中有类似以下的代码
class MyFragment : Fragment(), CoroutineScope by MainScope() {
override fun onDestroy() {
cancel()
super.onDestroy()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
doSomething()
}
private fun doSomething() = launch {
val data = withContext(Dispathers.IO) {
getData()
}
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
}
在生产环境中,访问NPEs
时,我在doSomething(
中得到了许多context
。
我的假设是,在coroutine
中调用cancel()
后,onDestroy()
被取消了,因此我不必费心检查context
是否为空值。但是看起来continues
甚至在调用cancel()
之后仍要执行。我认为,如果在完成cancel()
之后并在恢复withContext
之前调用coroutines
,就会发生这种情况。
所以我用以下内容替换了doSomething()
。
private fun doSomething() = launch {
val data = withContext(Dispathers.IO) {
getData()
}
if (isActive) {
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
}
这可以解决崩溃问题。
但是,这是预期的行为还是我做错了什么? Kotlin的文档对此不太清楚。在线上的大多数示例都类似于我的原始代码。
答案 0 :(得分:4)
您的代码假定withContext()
将在返回时停止执行,如果取消了作用域,但实际上并没有执行,直到kotlin协程的1.3.0版本。这是GitHub issue。我猜您正在使用该库的早期版本。
我还建议您使用LifecycleScope而不是自定义范围。它是lifecycle-runtime-ktx
库的一部分。因此,简化的解决方案如下所示:
// build.gradle
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc02"
}
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
doSomething()
}
private fun doSomething() = viewLifecycleOwner.lifecycleScope.launch {
val data = withContext(Dispathers.IO) {
getData()
}
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
}
还有其他有用的实用程序可以简化协程的使用,请查看Use Kotlin coroutines with Architecture components文档部分。
答案 1 :(得分:0)
这是预期的行为,因为协程取消本质上是合作的。
在您的情况下,虽然suspend函数将检查协程的状态,但您必须自己执行此操作。
一种更干净的方法是在视图模型中进行这些操作,然后在ViewModel onCleared
中取消协程;如果您使用的是ViewModel
答案 2 :(得分:0)
最新的协程更新引入了使用示波器安全工作的可能性。基本上,如果您要在Fragments或Activity中进行异步工作,请使用lifecycleScope
,如果要在ViewModel中进行异步工作,则需要使用viewModelScope
。还有一个GlobalScope可以在任何类中使用,但事实证明它具有许多稳定性缺陷,应避免使用。在任何情况下,都应该使用lifecycleScope或viewModelScope并在其他类中使用暂停函数。
所以这是这样做的方法:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launch {
doSomething()
}
}
private suspend fun doSomething(){
withContext(Dispatchers.IO){
getData()
if (isActive) {
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
}
}
如您在上面的代码中看到的,doSomething函数不需要作用域或启动,因为它充当在onActivityCreated中已启动的协程中执行的任务。在doSomething()函数中启动新的协程是没有意义的。同样,您可以将整个代码封装在withContext()语句中,因为获取共享首选项也是在Dispatchers.IO上运行效果最好的事务。另外,您可以随时将上下文更改为所需的上下文,例如:
withContext(Dispatchers.IO){
getData()
if (isActive) {
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
withContext(Dispatchers.Default){
// Heavy computing
}
}
始终记住,Dispatchers.IO最适合数据库事务,共享首选项和网络调用。如果您在forEach中进行诸如forEach之类的繁重计算以及诸如此类的工作,请使用Dispatchers.Default。如果您需要查看工作,请使用Dispatchers.Main。
这些作用域确保您的任务被执行而不会被其他活动组件中断,并且如果它被诸如活动销毁之类的干扰所中断,它可以防止异常并在范围内的所有内容上正确使用垃圾收集器。自从我开始使用示波器以来,我再也没有崩溃了。