Kotlin delay方法解析
本文记录了kotlin协程(Android)中delay方法的字节码实现,并解析了delay方法如何实现挂起操作。
一、delay方法介绍
1.1、delay方法使用举例
class TestDelay {suspend fun testDelay() {Log.d("TestDelay", "before delay")delay(1000)Log.d("TestDelay", "after delay")}
}
上述方法将协程挂起1s,然后再恢复执行。
1.2、delay方法如何实现挂起操作
delay的挂起操作有别于Thread.sleep操作,delay挂起不会导致线程暂停执行,sleep会导致线程暂停执行。
查看上述代码的字节码反义结果如下:
delay方法正常执行情况下, 会返回一个COROUTINE_SUSPENDED对象,此对象与var4对象相同,会使当前的方法直接返回,从而起到挂起作用。
二、delay方法实现代码
接下查看delay是如何返回COROUTINE_SUSPENDED对象的
2.1、kotlin层代码
public suspend fun delay(timeMillis: Long) {if (timeMillis <= 0) return // don't delayreturn suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->// if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.if (timeMillis < Long.MAX_VALUE) {cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)}}
}public interface Delay {/** @suppress **/@Deprecated(message = "Deprecated without replacement as an internal method never intended for public use",level = DeprecationLevel.ERROR) // Error since 1.6.0public suspend fun delay(time: Long) {if (time <= 0) return // don't delayreturn suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }}public fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =DefaultDelay.invokeOnTimeout(timeMillis, block, context)
}
查看协程代码(1.7.3版本)发现,delay方法会调用协程context中的scheduleResumeAfterDelay方法,此方法会等待一段时间,然后再resume协程。
这里出现一个问题----delay方法没有返回值,这与1.2中的方法好像有些冲突,在下一节查看delay方法的字节码,看下具体实现是如何,来确认是否真的有冲突。
这里先看下suspendCancellableCoroutine方法,其实现如下:
public suspend inline fun <T> suspendCancellableCoroutine(crossinline block: (CancellableContinuation<T>) -> Unit
): T =suspendCoroutineUninterceptedOrReturn { uCont ->val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)/** For non-atomic cancellation we setup parent-child relationship immediately* in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but* properly supports cancellation.*/cancellable.initCancellability()block(cancellable)cancellable.getResult()}@SinceKotlin("1.3")
@InlineOnly
@Suppress("UNUSED_PARAMETER", "RedundantSuspendModifier")
public suspend inline fun <T> suspendCoroutineUninterceptedOrReturn(crossinline block: (Continuation<T>) -> Any?): T {contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }throw NotImplementedError("Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic")
}
上图中的2个方法都用inline标注了,是内敛方法,所以在编译时会直接编译到delay方法中(注意:因为直接编译到delay方法中,所以内敛方法中最后一行的结果会被delay方法中的return使用)。同时看到在方法的最后调用了cancellable.getResult()方法,经查看代码发现,此方法内会返回COROUTINE_SUSPENDED对象。在此我们猜测此对象会作为delay方法的返回值,在下一节也会印证此猜测。
这样就了解了delay方法(编译后的字节码方法)的返回值对象是由何而来。针对kotlin源码中delay方法并没有返回值的问题,猜测在编译时delay方法会被编译成一个有返回值的方法,在下一节也会印证此猜测。
2.2、字节码
delay方法的字节码反义如下
@Nullablepublic static final Object delay(long timeMillis, @NotNull Continuation $completion) {if (timeMillis <= 0L)return Unit.INSTANCE; int $i$f$suspendCancellableCoroutine = 0;Continuation uCont$iv = $completion;int $i$a$-suspendCoroutineUninterceptedOrReturn-CancellableContinuationKt$suspendCancellableCoroutine$2$iv = 0;CancellableContinuationImpl<? super Unit> cancellable$iv = new CancellableContinuationImpl(IntrinsicsKt.intercepted(uCont$iv), 1);cancellable$iv.initCancellability();CancellableContinuation<? super Unit> cont = cancellable$iv;int $i$a$-suspendCancellableCoroutine-DelayKt$delay$2 = 0;if (timeMillis < Long.MAX_VALUE)getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont); if (cancellable$iv.getResult() == IntrinsicsKt.getCOROUTINE_SUSPENDED())DebugProbesKt.probeCoroutineSuspended($completion); if (cancellable$iv.getResult() == IntrinsicsKt.getCOROUTINE_SUSPENDED())return cancellable$iv.getResult(); cancellable$iv.getResult();return Unit.INSTANCE;}
可以看出delay方法在经过编译之后,会变成一个返回值为object的方法,同时在正常执行情况下,会将cancellable$iv.getResult()作为返回值,而这个方法也就是kotlin中的cancellable.getResult()方法。
三、总结
本文介绍了delay方法会被编译成一个有返回值的方法,并通过返回COROUTINE_SUSPENDED对象的方式,实现挂起。在挂起后,会等待一段时间,然后再调用协程的resume方法,恢复协程执行。此恢复操作,如果是在主线程会通过handler.postDelay方式实现;如果是在子线程,会通过一个DelayedTask实现,在此不进行具体分析。