携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
前言
当我们在activity或者Fragement中使用协程的话,一般情况下我们都需要显式的去调用CoroutineContext.cancel() 以在组件被销毁时取消正在运行的协程。但是这样一旦我们遗忘了这个非常容易被忽略的步骤的话,就有可能会带来很多程序异常的问题。
如何安全的执行协程?
如果您使用生命周期组件,则可以创建生命周期感知协程。一般在 ComponentActivity这类实现了 LifecycleOwner 的组件中,一般我们会使用生命周期协程作用域来启动我们的协程。
lifecycleScope.launch {
// 协程体
}
在生命周期范围内执行的协程是安全的,因为它们根据生命周期安全地终止。原因可以通过查看 LifecycleCoroutineScopeImpl 代码内部来猜测。
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel()
}
}
// 添加了LifecycleEventObserver接口的监听
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
// 判断当前生命周期是destroyed的时候,移除监听,并取消协程
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
}
当register方法被触发的时候便注册上LifecycleEventObserver接口监听事件,在onStateChanged回调中,判断当前生命周期是destroyed的时候,移除监听,并取消协程
如果收到 Activity 或 Fragment 生命周期事件并确定其被销毁,则可以确认子协程任务被取消。这样就可以有效的避免内存泄漏了。
注意:ViewModel 有单独的生命周期,但可以使用 viewModelScope 代替生命周期范围。
如何根据生命周期执行协程
有时您可能想要停止协程的执行,除非生命周期处于特定状态。共有三个函数: whenCreated、whenStarted、whenResumed,顾名思义就是一个智能函数,根据每个函数后面的后缀对应的生命周期执行,如果生命周期的状态不满足则停止。以下示例代码在生命周期状态为最小 RESUMED 状态时执行。
class TestActivity : AppCompatActivity() {
val counterFlow:Flow<Int> = flow{
var counter = 0
while(true){
delay(1000)
emit(counter++)
}
}
override fun onCreated(savedInstanceState: Bundle?) {
// 一种写法
lifecycleScope.launch {
whenResumed{
counterFlow.collect {
Log.i("dididi", "$it")
}
}
}
// 另外一种
lifecycleScope.launchWhenResumed {
counterFlow.collect {
Log.i("dididi", "$it")
}
}
}
}
执行完上面的示例代码后,按中间的home键让app进入后台状态,然后再次进入app进入前台状态,可以在日志中查看有哪些变化。
dididi: 1
dididi: 2
// 进入后台,止工作
// 从新恢复前台,继续工作
dididi: 3
dididi: 4
如何根据生命周期重启协程
我们通过 whenXXX 函数调用学习了如何根据生命周期自动启动/停止/恢复协程。但是,有时您想根据生命周期启动/取消/重新启动协程。在这种情况下,Lifecycle 和 LifecycleOwner 提供了一个名为 repeatOnLifecycle 的扩展函数。以下示例显示了在生命周期状态为 RESUMED 时收集新流。
class TestActivity : AppCompatActivity() {
val counterFlow:Flow<Int> = flow{
var counter = 0
while(true){
delay(1000)
emit(counter++)
}
}
override fun onCreated(savedInstanceState: Bundle?) {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED){
counterFlow.collect {
Log.i("dididi", "$it")
}
}
}
}
}
再次,在执行完示例代码后,按中间的home键让app进入后台状态,然后再次进入app进入前台状态。
dididi: 1
dididi: 2
// 进入后台,止工作
// 从新恢复前台,继续工作
dididi: 1
dididi: 2