kotlin协程的生命周期与jetpack组件绑定
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kotlin协程的生命周期与jetpack组件绑定相关的知识,希望对你有一定的参考价值。
参考技术A 安卓上, 协程可以帮忙解决两大问题:管理长时间运行的任务, 这些任务可能阻塞主线程, 导致 UI 卡顿.
在主线程上安全地调用网络或磁盘操作.
安卓上使用协程的最好方式是使用官方的架构组件, 它们提供了对协程的支持. 目前 ViewModel, Lifecycle, LiveData , Room 组件提供了对协程一等的支持.
1、 ViewModel ViewModelScope
对 ViewModel 的支持主要是在 ViewModel 上提供了一个称为 ViewModelScope 的 CoroutineScope , 所有在 ViewModelScope 上启动的协程, 当 ViewModelScope 销毁时自动取消. 这样可以有效防止忘记取消任务时导致的资源泄漏.
其实 viewModelScope 的实现非常简单, 就是一个带有 Dispatchers.Main 的 SupervisorJob, 当 ViewModel.clear() 时, 在里面调用 Job.cancel() , 因为结构化并发的原因, 所有在 viewModelScope 范围内启动的协程, 都会级联取消.
2、Lifecycle LifecycleScope
每个具有生命周期的对象(Lifecycle)都有一个 LifecycleScope , 所有在它的范围内启动的协程, 当生命周期对象销毁时, 都会取消. 生命周期对象的 CoroutineScope 可以通过 lifecycle.coroutineScope 或者 lifecycleOwner.lifecycleScope 属性获取.
3、挂起生命周期相关的协程
在 Activity 或者 Fragment 中, 我们有时需要等到某个生命周期方法时, 或者至少在某个生命周期方法之后才执行某一任务, 如页面状态至少要 STARTED 才可以执行 FragmentTransaction , 对这种需求, 生命周期组件也提供了支持. Lifecycle 提供了 lifecycle.whenCreated, lifecycle.whenStarted, lifecycle.whenResumed 三个方法, 运行在这些方法内的协程, 如果页面的状态不是至少处于要求的最小状态, 协程将会挂起运行.
如果协程通过上面的whenXXX方法启动后, 处于活动状态, 还没有结束, 这时页面销毁了, 则协程会自动取消, 并且会走到下面的 finally 块中, 所在 finally 中, 需要检查页面所处的状态, 再决定做什么动作.
这里要注意: 如果页面 restart 重启了, 但协程并不会重启, 总之要确保信息是正确的.
4、LiveData 中使用协程
5、Room 对协程的支持
Room 从 v2.1 开始支持协程
JetPack知识点实战系列三:使用 Coroutines, Retrofit, Moshi实现网络数据请求
kotlin协程生命周期-基础
一、Job生命周期
通过
1.Job与async
CoroutineScope的扩展函数launch返回一个Job对象。
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
public interface Job : CoroutineContext.Element
public companion object Key : CoroutineContext.Key<Job>
public val isActive: Boolean
......
CoroutineScope的扩展函数async返回一个Deferred对象,Deferred 继承 Job。
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
Deferred 继承 Job,同样是一个接口,Deferred 有一个泛型参数T,并且有一个关键的await()方法,其返回值类型是T。
public interface Deferred<out T> : Job
public suspend fun await(): T
......
2.job.cancel()
fun main()
runBlocking
val job = launch
println("launch start")
delay(2000L)
println("launch end")
job.printlog()
job.cancel()
job.printlog()
fun Job.printlog()
printCoroutine("isActive:$isActive, isCancelled:$isCancelled, isCompleted:$isCompleted")
fun printCoroutine(any: Any?)
println("" + any + ";Thread:" + Thread.currentThread().name)
输出log 如下:
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
isActive:false, isCancelled:true, isCompleted:false;Thread:main @coroutine#1
上面的代码中,通过job.cancel()取消协程任务,然后通过job.isCancelled查看状态为true。代表了协程任务处于取消状态。所以launch中的协程任务没有执行就取消了。
对上面的代码做简单修改:在job执行cancel前delay 1000ms,可以发现launch任务执行过程中被取消了。
runBlocking
val job = launch
println("launch start")
delay(2000L)
println("launch end")
job.printlog()
delay(1000L)
job.cancel()
job.printlog()
输出log:
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
launch start
isActive:false, isCancelled:true, isCompleted:false;Thread:main @coroutine#1
如果在上述代码中delay(2000L)后再次执行 job.printlog()。可以发现此时:isCompleted:true。
runBlocking
val job = launch
println("launch start")
delay(2000L)
println("launch end")
job.printlog()
delay(1000L)
job.cancel()
job.printlog()
delay(2000)
job.printlog()
Log输出如下:
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
launch start
isActive:false, isCancelled:true, isCompleted:false;Thread:main @coroutine#1
isActive:false, isCancelled:true, isCompleted:true;Thread:main @coroutine#1
3.job.start()
fun main()
runBlocking
val job = launch(start = CoroutineStart.LAZY)
printCoroutine("Coroutinue start!")
delay(2000L)
printCoroutine("Coroutinue end!")
delay(1000L)
job.printlog()
job.start()
job.printlog()
delay(1000L)
job.cancel()
delay(1000L)
job.printlog()
结果:
isActive:false, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
Coroutinue start!;Thread:main @coroutine#2
isActive:false, isCancelled:true, isCompleted:true;Thread:main @coroutine#1
将启动模式设置为Coroutinue.LAZY,协程任务被launch以后,可以发现isActive为false,不会立即执行,当执行job.start()之后,isActive为true,所以Coroutinue.LAZY是一种懒加载的行为模式。所以通过Log可以发现只有调用了job.start()以后,job的状态才变成了Active。调用了cancel以后,job的状态才会变成isCancelled、isCompleted。
协程的Job有两种初始状态,如果Job是以懒加载的方式创建,那么它的初始状态是New;如果协程以非懒加载的方式创建,它的初始状态是Active。
注意:当调用cancel以后,isCancelled = true,然后isCompleted = true,说明对于协程来说,取消协程,也是一种“结束状态”。
New、Active、Completing、Cancelling、Completed、Cancelled 这些状态,都是 Job 内部私有的状态。而 Job 对外暴露出的 isCompleted 并不是与其一一对应的。Job 内部私有的 Completed、Cancelled 状态,都会认为是外部的 isCompleted 状态。
4.协程正常结束的生命周期
fun main()
runBlocking
val job = launch(start = CoroutineStart.LAZY)
printCoroutine("Coroutinue start!")
delay(1000L)
printCoroutine("Coroutinue end!")
delay(500L)
job.printlog()
job.start()
job.printlog()
delay(2000L)
job.printlog()
delay(2000L)
printCoroutine("Process end!")
输出:
isActive:false, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
Coroutinue start!;Thread:main @coroutine#2
Coroutinue end!;Thread:main @coroutine#2
isActive:false, isCancelled:false, isCompleted:true;Thread:main @coroutine#1
Process end!;Thread:main @coroutine#1
从Log可以发现:isActive从false到true;isCompleted从false到true。
5.Job内部delay大于外部delay
runBlocking
val job = launch(start = CoroutineStart.LAZY)
printCoroutine("Coroutinue start!")
delay(6000L)
printCoroutine("Coroutinue end!")
delay(500L)
job.printlog()
job.start()
job.printlog()
delay(1000L)
job.printlog()
delay(1000L)
printCoroutine("Process end!")
输出:
isActive:false, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
Coroutinue start!;Thread:main @coroutine#2
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
Process end!;Thread:main @coroutine#1
Coroutinue end!;Thread:main @coroutine#2
在上面的代码中,将job内部的delay改为6000L后,runBlocking会一直阻塞,等到job任务执行完毕以后才能真正退出。
6.等待和监听协程的结束事件
runBlocking
suspend fun download()
printCoroutine("Start Download...")
delay(4000L)
printCoroutine("End Download...")
val job = launch(start = CoroutineStart.LAZY)
download()
delay(200L)
job.printlog()
job.start()
job.printlog()
job.invokeOnCompletion
job.printlog()
job.join()
printCoroutine("Process End!")
Log:
isActive:false, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
isActive:true, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
Start Download...;Thread:main @coroutine#2
End Download...;Thread:main @coroutine#2
isActive:false, isCancelled:false, isCompleted:true;Thread:main @coroutine#2
Process End!;Thread:main @coroutine#1
通过Log可以发现,invokeOnCompletion可以用来监听协程结束的事件,即使job取消,invokeOnCompletion仍然会被调用。
在上面代码中job.start()执行以后执行job.cancel()。可以发现:即使job取消,invokeOnCompletion仍然会被调用。
runBlocking
suspend fun download()
printCoroutine("Start Download...")
delay(4000L)
printCoroutine("End Download...")
val job = launch(start = CoroutineStart.LAZY)
download()
delay(200L)
job.printlog()
job.start()
job.cancel()
job.printlog()
job.invokeOnCompletion
job.printlog()
job.join()
printCoroutine("Process End!")
isActive:false, isCancelled:false, isCompleted:false;Thread:main @coroutine#1
isActive:false, isCancelled:true, isCompleted:false;Thread:main @coroutine#1
isActive:false, isCancelled:true, isCompleted:true;Thread:main @coroutine#2
Process End!;Thread:main @coroutine#1
job.join() 其实是一个“挂起函数”,它的作用就是:挂起当前的程序执行流程,等待 job 当中的协程任务执行完毕,然后再恢复当前的程序执行流程。
7.Job源码
public interface Job : CoroutineContext.Element
// ------------ 状态查询API ------------
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
public fun getCancellationException(): CancellationException
// ------------ 操控状态API ------------
public fun start(): Boolean
public fun cancel(cause: CancellationException? = null)
public fun cancel(): Unit = cancel(null)
public fun cancel(cause: Throwable? = null): Boolean
// ------------ 等待状态API ------------
public suspend fun join()
public val onJoin: SelectClause0
// ------------ 完成状态回调API ------------
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
public fun invokeOnCompletion(
onCancelling: Boolean = false,
invokeImmediately: Boolean = true,
handler: CompletionHandler): DisposableHandle
8.Deferred
await()方法
runBlocking
val deferred = async
printCoroutine("Coroutinue Start!")
delay(1000L)
printCoroutine("Coroutinue End!")
"result"
val result = deferred.await()
println("Result:$result")
printCoroutine("Process End!")
Coroutinue Start!;Thread:main @coroutine#2
Coroutinue End!;Thread:main @coroutine#2
Result:result
Process End!;Thread:main @coroutine#1
deferred.await() 可以获取协程的执行结果,它还会阻塞当前协程的执行流程,直到协程任务执行完毕。在这一点的行为上,await() 和 join() 是类似的。
public interface Deferred<out T> : Job
public suspend fun await(): T
... ...
await() 是一个挂起函数,拥有挂起和恢复的能力。如果当前的 Deferred 任务还没执行完毕,那么,await() 就会挂起当前的协程执行流程,等待 Deferred 任务执行完毕,再恢复执行后面剩下的代码。而job.join() 在协程执行完毕之前,后面的协程代码都被暂时挂起了,等到协程执行完毕,才有机会继续执行。
二、Job 与结构化并发
runBlocking
val parentJob: Job
var job1: Job? = null
var job2: Job? = null
var job3: Job? = null
parentJob = launch
job1 = launch
delay(1000L)
job2 = launch
delay(2000L)
job3 = launch
delay(3000L)
delay(500L)
parentJob.children.forEachIndexed index, job ->
when (index)
0 -> println("job1 === job is $job1 === job")
1 -> println("job2 === job is $job2 === job")
2 -> println("job2 === job is $job3 === job")
parentJob.join()
printCoroutine("Process End!")
job1 === job is true
job2 === job is true
job2 === job is true
Process End!;Thread:main @coroutine#1
parentJob 是最外层的 launch 返回的对象,在这个 launch 的内部,嵌套了三个 launch,它们的 Job 对象分别赋值给了 job1、job2、job3。在parentJob.children遍历过程中,发现job1、job2、job3 其实就是 parentJob 的 children。也就是说,我们使用 launch 创建出来的协程,是存在父子关系的。
public interface Job : CoroutineContext.Element
// ------------ parent-child ------------
public val children: Sequence<Job>
@InternalCoroutinesApi
public fun attachChild(child: ChildJob): ChildHandle
每个 Job 对象,都会有一个Sequence类型的 children 属性,是一个惰性的集合,我们可以对它进行遍历。而 attachChild() 则是一个协程内部的 API,用于绑定 ChildJob 。
通过调用parentJob 的 join() 方法,会等待其内部的 job1、job2、job3 全部执行完毕,才会恢复执行。
runBlocking
val parentJob: Job
var job1: Job? = null
var job2: Job? = null
var job3: Job? = null
parentJob = launch
job1 = launch
printCoroutine("job1 start")
delay(1000L)
printCoroutine("job1 end")
job2 = launch
printCoroutine("job2 start")
delay(2000L)
printCoroutine("job2 end")
job3 = launch
printCoroutine("job3 start")
delay(3000L)
printCoroutine("job3 end")
delay(500L)
parentJob.children.forEachIndexed index, job ->
when (index)
0 -> println("job1 === job is $job1 === job")
1 -> println("job2 === job is $job2 === job")
2 -> println("job2 === job is $job3 === job")
parentJob.cancel()
printCoroutine("Process End!")
job1 start;Thread:main @coroutine#3
job2 start;Thread:main @coroutine#4
job3 start;Thread:main @coroutine#5
job1 === job is true
job2 === job is true
job2 === job is true
Process End!;Thread:main @coroutine#1
parentJob 调用cancel() 方法,job1、job2、job3的协程任务也全都被取消了。
三、async使用场景
runBlocking
suspend fun getResult1(): String
delay(1000L)
return "Result1"
suspend fun getResult2(): String
delay(1000L)
return "Result2"
suspend fun getResult3(): String
delay(1000L)
return "Result3"
val results = mutableListOf<String>()
val time = measureTimeMillis
results.add(getResult1())
results.add(getResult2())
results.add(getResult3())
println("Time:$time")
println(results)
Time:3052
[Result1, Result2, Result3]
函数总耗时未3052毫秒,可以使用async进行优化。
runBlocking
suspend fun getResult1(): String
delay(1000L)
return "Result1"
suspend fun getResult2(): String
delay(1000L)
return "Result2"
suspend fun getResult3(): String
delay(1000L)
return "Result3"
val results = mutableListOf<String>()
val time = measureTimeMillis
val result1 = async
getResult1()
val result2 = async
getResult1()
val result3 = async
getResult1()
results.add(result1.await())
results.add(result2.await())
results.add(result3.await())
println("Time:$time")
println(results)
Time:1029
[Result1, Result1, Result1]
可以看到 async可以用来优化一些并发操作。
以上是关于kotlin协程的生命周期与jetpack组件绑定的主要内容,如果未能解决你的问题,请参考以下文章
深入理解Kotlin协程协程的上下文 CoroutineContext
Kotlin 协程协程启动 ⑥ ( 协程生命周期状态 | 新创建 New | 活跃 Active | 完成中 Completing | 已完成 Completed | 取消中 | 已取消 )
Kotlin 协程协程启动 ⑥ ( 协程生命周期状态 | 新创建 New | 活跃 Active | 完成中 Completing | 已完成 Completed | 取消中 | 已取消 )