invokeOnCompletion 监听协程状态
本文先只看 GlobalScope.launch 创建的根 StandaloneCoroutine:正常完成、异常完成时,invokeOnCompletion 和 CoroutineExceptionHandler 分别在什么时候触发。父子协程之间的异常传播先不展开,后面单独看。
在看异常处理之前,先从 invokeOnCompletion 入手。这个例子里没有父 Job,重点是看完成回调如何挂到当前 Job 上:
|
|
执行结果:
|
|
invokeOnCompletion是 Job 完成时的回调。正常完成时cause=null,异常或取消完成时cause会带上对应原因。
通过 launch 创建协程时,内部会构造一个 StandaloneCoroutine。它本身就是一个 Job,launch 返回的也是这个 Job:
StandaloneCoroutine 继承自 AbstractCoroutine:

GlobalScope.launch 没有父 Job,所以 initParentJob 里拿不到 parent,parentHandle 会被设置成 NonDisposableHandle。
launch 返回后,接着调用 job.invokeOnCompletion。这个方法最终在 JobSupport 中实现:

makeNode 的实现如下:
从调用链看,onCancelling=false,当前 handler 还不是 JobNode,所以会创建 InvokeOnCompletion,并把外部传进来的 CompletionHandler 包进去:
|
|
InvokeOnCompletion 是一个 JobNode:
|
|
JobNode 继承了 CompletionHandlerBase:
|
|
CompletionHandlerBase 实现了 CompletionHandler。所以 InvokeOnCompletion.invoke(cause) 被调用时,会转调外部传进来的 CompletionHandler.invoke(cause)。这里可以理解成给业务回调包了一层 Job 节点。
结论:
makeNode返回的是一个InvokeOnCompletion对象,它既是JobNode,也保存着业务侧的CompletionHandler。
回到 invokeOnCompletion 的实现。创建 InvokeOnCompletion 后,会进入 loopOnState 遍历当前 state。这个例子的初始 state 是 Empty,并且 state.isActive=true,所以最终会把 InvokeOnCompletion CAS 到当前 Job 的 state 上,并返回这个节点。
结论: 调用
job.invokeOnCompletion后,业务侧的CompletionHandler会先被包成InvokeOnCompletion,再挂到 Job 的 state 上。Job 完成时触发这个节点,最后回到业务侧的回调。
协程体执行完后的 Job 流程:
前面分析过,协程体会被编译成 SuspendLambda。它的 invokeSuspend 执行完后,会继续调用上层 continuation 的 resumeWith。在这个例子里,上层 continuation 就是 StandaloneCoroutine,也是 launch 返回的 Job:
|
|
协程体的结果会传给 makeCompletingOnce:
|
|
这里会轮询读取 state,并调用 tryMakeCompleting:
前面已经把 InvokeOnCompletion 挂到了 state 上,所以这里的 state 是一个 JobNode。当前例子没有子 Job,并且 proposedUpdate 不是 CompletedExceptionally,所以会走 tryFinalizeSimpleState:
内部调用 completeStateFinalization:
|
|
completeStateFinalization 会判断 state 是否是 JobNode。当前 state 是 InvokeOnCompletion,所以直接调用 state.invoke(cause),再回调到外部传入的 CompletionHandler。正常完成时 cause=null,所以日志里能看到 invokeOnCompletion 被打印出来。
invokeOnCompletion 监听 Job 异常
还是看一个根 launch 例子:
|
|
这里给 launch 的 context 放了一个 CoroutineExceptionHandler,先看日志:
|
|
invokeOnCompletion 也能拿到异常。区别在于,异常完成不会走 tryFinalizeSimpleState 这条快路径,而是进入 tryMakeCompletingSlowPath:
非异常完成会走 tryFinalizeSimpleState;异常完成会走 tryMakeCompletingSlowPath,里面会进入 finalizeFinishingState:
这个例子里当前协程是根 StandaloneCoroutine,没有普通父 Job 可以处理异常,所以 cancelParent 返回 false,接着会调用自己的 handleJobException。StandaloneCoroutine 重写了这个方法:

然后进入 handleCoroutineException:

对根
launch来说,如果 context 里有CoroutineExceptionHandler,异常会先交给它处理;如果没有,就会走默认未捕获异常处理,Android 上通常表现为崩溃。
再回到 invokeOnCompletion 的触发时机。异常会先在 finalizeFinishingState 里处理,之后仍会调用 completeStateFinalization,最终触发 invokeOnCompletion 注册进来的 CompletionHandler。这也是日志里 CoroutineExceptionHandler 和 invokeOnCompletion 都能拿到异常的原因。
协程异常被 try-catch 接住:
|
|
日志如下:
|
|
从日志看,这次 CoroutineExceptionHandler 没有回调,invokeOnCompletion 里拿到的异常也是 null。
我们通过字节码可以看到原因:
异常已经在协程体内被 catch 住,传给 JobSupport 的结果就不是 CompletedExceptionally,所以后续和正常完成一样处理。
总结:
注册阶段:launch 创建根 StandaloneCoroutine,没有父 Job 时 parentHandle=NonDisposableHandle。调用 invokeOnCompletion 时,外部的 CompletionHandler 会被包成 InvokeOnCompletion,再作为 JobNode 挂到 Job 的 state 上。
正常完成:SuspendLambda.invokeSuspend 执行结束后,会调用上层 continuation,也就是 StandaloneCoroutine.resumeWith。流程进入 makeCompletingOnce -> tryMakeCompleting -> tryFinalizeSimpleState -> completeStateFinalization,最后触发 InvokeOnCompletion。这时 cause=null。
异常完成:如果协程体抛出的异常没有在内部 catch 住,resumeWith(Result.failure) 会让 Job 进入 tryMakeCompletingSlowPath -> finalizeFinishingState。根 StandaloneCoroutine 没有父 Job 接手时,会调用自己的 handleJobException,再从 context 里找 CoroutineExceptionHandler。异常处理之后,completeStateFinalization 仍会触发 InvokeOnCompletion,所以回调里可以拿到同一个异常。
try-catch:如果异常已经在协程体内部 catch 住,传给 Job 的就是正常结果,CoroutineExceptionHandler 不会回调,invokeOnCompletion 收到的 cause 也是 null。
InvokeOnCompletion 类图

异常处理时序图
