suspendCoroutine
在android中无处不在获取view的宽高,而获取宽高是需要在view绘制完后才能获取,所以这是一个时机问题,通常通过view.post来获取,那么用协程如何形如同步获取宽高呢?下面来试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
GlobalScope.launch {
val wh = getViewWh(view)
Log.d(TAG, "width:${wh.first}")
Log.d(TAG, "height:${wh.second}")
}
Log.d(TAG, "outer coroutine")
private suspend fun getViewWh(view: View) = suspendCoroutine<Pair<Int, Int>> { continuation ->
view.post {
val width = view.width
val height = view.height
continuation.resume(Pair(width, height))
}
}
|
日志如下:
1
2
3
|
11:00:57.708 D outer coroutine
11:00:57.810 D width:1080
11:00:57.810 D height:2206
|
使用了suspendCoroutine方法,方法的返回值,是suspendCoroutine指定的泛型。
可以看到日志正常获取,下面来看看字节码是如何实现的:
launch启动的时候,内部的协程代码块编译结果是SuspendCoroutineActivity$onCreate$1:
调用了SuspendCoroutineActivity的静态方法access$getViewWh:
静态方法是直接调用了成员方法getViewWh,它是本次的重要实现:
传进来的Continuation对应了SuspendCoroutineActivity$onCreate$1,它是传给了SafeContinuation,此处注意到调用了continuation的intercepted方法,它是continuation的扩展方法,它是在continuationImpl中实现了,它实际是看context中是否有dispatcher,这块在协程切换线程中讲过,它实际是生成了一个CoroutineDispatcher。
接着看又调用了SuspendCoroutineActivity$getViewWh$2$1这个suspendLambda,并把SafeContinuation传进去了:
此处的Runnable就对应了post中的代码块,最终在run方法中调用了continuation的resumeWith方法,并把宽高回调出去了。此处的continuation是SafeContinuation,看下它的resumeWith方法:
此处的result默认值是UNDECIDED:
由于默认值是UNDECIDED,在上面getViewWh中先调用了safeContinuation的getOrThrow方法:
所以会给result设置上了COROUTINE_SUSPENDED标记,所以在第一个suspendLambda的invokeSuspend方法中能被挂起,等到执行safeContinuation的resumeWith的时候,第一个suspendLambda就恢复了。最终获取到结果,整个流程就结束。
suspendCoroutine的回调中还有一个resumeWithException方法,用于返回失败的结果,如果返回失败的时候,需要捕捉异常。
suspendCancellableCoroutine
它和suspendCoroutine区别是回调中是一个CancellableContinuationImpl,它提供了cancel方法,当cancel的时候,程序不会崩溃。它会把cancel的结果分发到delegate中,此处的delegate其实就是对应了GlobalScope.launch启动时的suspendLambda,最后它会分发到父job中。看下CancellableContinuationImpl的cancel方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public override fun cancel(cause: Throwable?): Boolean {
_state.loop { state ->
if (state !is NotCompleted) return false // false if already complete or cancelling
// Active -- update to final state
val update = CancelledContinuation(this, cause, handled = state is CancelHandler || state is Segment<*>)
if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
// Invoke cancel handler if it was present
when (state) {
is CancelHandler -> callCancelHandler(state, cause)
is Segment<*> -> callSegmentOnCancellation(state, cause)
}
// Complete state update
detachChildIfNonResuable()
dispatchResume(resumeMode) // no need for additional cancellation checks
return true
}
}
|
此处会构造出CancelledContinuation,它是CompletedExceptionally对象,这个其实就是协程异常的扩展类,最终把该对象设置到CancellableContinuationImpl的state上。
一路跟到DispatchedTask的dispatch方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
internal fun <T> DispatchedTask<T>.dispatch(mode: Int) {
assert { mode != MODE_UNINITIALIZED } // invalid mode value for this method
val delegate = this.delegate
val undispatched = mode == MODE_UNDISPATCHED
if (!undispatched && delegate is DispatchedContinuation<*> && mode.isCancellableMode == resumeMode.isCancellableMode) {
// dispatch directly using this instance's Runnable implementation
val dispatcher = delegate.dispatcher
val context = delegate.context
if (dispatcher.isDispatchNeeded(context)) {
dispatcher.dispatch(context, this)
} else {
resumeUnconfined()
}
} else {
// delegate is coming from 3rd-party interceptor implementation (and does not support cancellation)
// or undispatched mode was requested
resume(delegate, undispatched)
}
}
|
此处会走dispatcher.dispatch逻辑,因为上面的GlobalScope.launch其实创建的dispatcher是一个Dispatcher.Default类型的。DispatchedTask是一个runnable接口,看下它的run方法:
最终也是通过continuation的resumeWithException方法回调出去,然后交给了父job去处理,此时的exception是一个CancellationException,其实上面的suspendCoroutine也可以通过resumeWithException回调一个CancellationException,程序也不会崩溃。
参考:https://juejin.cn/post/7121517604644061192