invokeOnCompletion监听协程状态
在了解协程的异常处理之前,先来熟悉下协程之间job是如何绑定关系,还是通过一个例子来熟悉他们之间的关系:
1
2
3
4
5
6
7
8
|
private fun demo(){
val job = GlobalScope.launch {
Log.d(TAG, "demo: launch代码")
}
job.invokeOnCompletion {
Log.d(TAG, "demo: invokeOnCompletion")
}
}
|
执行结果:
1
2
|
CoroutineJobActivity com.example.coroutinescopedemo D demo: launch代码
CoroutineJobActivity com.example.coroutinescopedemo D demo: invokeOnCompletion
|
可以看出来invokeOnCompletion是job执行完毕的回调。
通过launch创建协程内部会构造一个StandaloneCoroutine,它就是一个job,并且launch返回的就是该job:
StandaloneCoroutine继承自AbstractCoroutine:
因此parentHandle为NonDisposableHandle,然后方法结束。
此时执行完launch后,继续执行了job的invokeOnCompletion方法,它是一个抽象方法,实现方法在jobSupport类:
我们看下makeNode方法实现:
从调用链上看onCancelling传进来的是false,并且此时的handler还不是一个JobNode节点,所以会创建了一个InvokeOnCompletion对象,并把外面传进来的ComppletionHandler传到InvokeOnCompletion中,看下InvokeOnCompletion对象:
1
2
3
4
5
|
private class InvokeOnCompletion(
private val handler: CompletionHandler
) : JobNode() {
override fun invoke(cause: Throwable?) = handler.invoke(cause)
}
|
InvokeOnCompletion是一个JobNode节点:
1
2
3
4
5
6
7
8
9
10
|
internal abstract class JobNode : CompletionHandlerBase(), DisposableHandle, Incomplete {
/**
* Initialized by [JobSupport.makeNode].
*/
lateinit var job: JobSupport
override val isActive: Boolean get() = true
override val list: NodeList? get() = null
override fun dispose() = job.removeNode(this)
override fun toString() = "$classSimpleName@$hexAddress[job@${job.hexAddress}]"
}
|
JobNode实现了CompletionHandlerBase抽象类:
1
2
3
|
internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler {
actual abstract override fun invoke(cause: Throwable?)
}
|
它也是实现了CompletionHandler接口,CompletionHandler是一个闭包,从上面继承以及JobNode实现来看,在它的invoke实现里面,会触发传进来的CompletionHandler的invoke实现,从实现来看其实这就是个静态代理的模式。
结论:
makeNode内部会返回一个InvokeOnCompletion对象,它是一个JobNode节点。
回到上面的invokeOnComletion的实现,创建了InvokeOnCompletion后,会走到loopOnState的函数,它是遍历state,它的初始状态是一个Empty状态,并且state.isActive=true。所以最终把刚才创建的InvokeOnCompletion设置到了state上,并且返回了该InvokeOnCompletion。
结论:
当调用了job的invokeOnCompletion方法后,会把外界创建好的CompletionHandler传给了InvokeCompletion,InvokeCompletion是一个JobNode,在它的invoke实现中,会回调到外界的CompletionHandler中。并且把创建的InvokeCompletion设置到当前job的state上了。
协程体执行完job的执行:
在之前分析协程体其实是一个SuspendLambda,在它的invokeSuspend调用完后,会执行它的continuation的resumeWith方法,在上面例子中其实是StandaloneCoroutine,它是一个job,所以看下它的resumeWith实现:
1
2
3
4
5
|
public final override fun resumeWith(result: Result<T>) {
val state = makeCompletingOnce(result.toState())
if (state === COMPLETING_WAITING_CHILDREN) return
afterResume(state)
}
|
将协程体的结果传到了makeCompletingOnce方法中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
internal fun makeCompletingOnce(proposedUpdate: Any?): Any? {
loopOnState { state ->
val finalState = tryMakeCompleting(state, proposedUpdate)
when {
finalState === COMPLETING_ALREADY ->
throw IllegalStateException(
"Job $this is already complete or completing, " +
"but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull
)
finalState === COMPLETING_RETRY -> return@loopOnState
else -> return finalState // COMPLETING_WAITING_CHILDREN or final state
}
}
}
|
轮训获取state,并调用了tryMakeCompleting方法:
从上面的分析知道state它是一个JobNode,它是一个InvokeCompletion,它不是一个ChildHandleNode,并且proposedUpdate不是CompletedExceptionally,所以会执行tryFinalizeSimpleState:
内部调用了completeStateFinalization方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
private fun completeStateFinalization(state: Incomplete, update: Any?) {
/*
* Now the job in THE FINAL state. We need to properly handle the resulting state.
* Order of various invocations here is important.
*
* 1) Unregister from parent job.
*/
parentHandle?.let {
it.dispose() // volatile read parentHandle _after_ state was updated
parentHandle = NonDisposableHandle // release it just in case, to aid GC
}
val cause = (update as? CompletedExceptionally)?.cause
/*
* 2) Invoke completion handlers: .join(), callbacks etc.
* It's important to invoke them only AFTER exception handling and everything else, see #208
*/
if (state is JobNode) { // SINGLE/SINGLE+ state -- one completion handler (common case)
try {
state.invoke(cause)
} catch (ex: Throwable) {
handleOnCompletionException(CompletionHandlerException("Exception in completion handler $state for $this", ex))
}
} else {
state.list?.notifyCompletion(cause)
}
}
|
该方法中判断state是否是JobNode,如果是的话,会调用invoke方法。在上面分析过state是一个InvokeOnCompletion对象,在它的invoke里面会回调到传进来的CompletionHandler的invoke中,所以在上面例子中打印了job执行完成的日志。
invokeOnCompletion监听job的异常
还是通过一个例子来分析下:
1
2
3
4
5
6
7
8
9
10
11
12
|
private fun demo3(){
val exceptionHandler =CoroutineExceptionHandler{ coroutineContext, throwable ->
Log.d(TAG, "demo3: CoroutineExceptionHandler--异常信息:${throwable?.message}")
}
val job = GlobalScope.launch(exceptionHandler) {
Log.d(TAG, "demo3: launch代码")
throw RuntimeException("协程体里面发生异常了")
}
job.invokeOnCompletion {
Log.d(TAG, "demo3: invokeOnCompletion--异常信息:${it?.message}")
}
}
|
此处用了一个CoroutineExceptionHandler方法,来创建CoroutineExceptionHandler对象来拦截异常的,这块先不用管,我们看下日志:
1
2
3
|
com.example.coroutinescopedemo D demo3: launch代码
com.example.coroutinescopedemo D demo3: CoroutineExceptionHandler--异常信息:协程体里面发生异常了
com.example.coroutinescopedemo D demo3: invokeOnCompletion--异常信息:协程体里面发生异常了
|
可以看出来,invokeOnCompletion中也能收到异常的消息。
但是异常和正常收数据可能不太一样,主要区别是在resumeWith之后的处理不太一样:
如果是非异常的话,会走tryFinalizeSimpleState;如果是异常的时候会走tryMakeCompletingSlowPath,在该方法里面会触发finalizeFinishingState方法,该方法有个很重要逻辑:
由于cancelParent返回false,所以会触发handleJobException,StandaloneCoroutine重写了该方法:
handleCoroutineException:
如果context中CoroutineExceptionHandler不为空,则异常不会往上抛了,所以此例中定义了context的CoroutineExceptionHandler程序不会崩溃。
回到上面再来看invokeOnCompletion的CompletionHandler触发时机,它是在finalizeFinishingState中触发,接着会调用到completeStateFinalization,最终在里面触发了invokeOnCompletion传进来的CompletionHandler。
协程异常try住:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private fun demo4() {
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.d(TAG, "demo4: CoroutineExceptionHandler--异常信息:${throwable?.message}")
}
val job = GlobalScope.launch(exceptionHandler) {
try {
Log.d(TAG, "demo4: launch代码")
throw RuntimeException("协程体里面发生异常了")
} catch (e: Exception) {
Log.d(TAG, "demo4: catch中代码")
}
}
job.invokeOnCompletion {
Log.d(TAG, "demo4: invokeOnCompletion--异常信息:${it?.message}")
}
}
|
日志如下:
1
2
3
|
com.example.coroutinescopedemo D demo4: launch代码
com.example.coroutinescopedemo D demo4: catch中代码
com.example.coroutinescopedemo D demo4: invokeOnCompletion--异常信息:null
|
从日志中看,此处的CoroutineExceptionHandler不回调,并且invokeOnCompletion收不到异常信息。
我们通过字节码可以看到原因:
此处的异常catch住后,给到JobSupport就不是异常了,所以按照成功的处理方式一样。