1 /*
<lambda>null2 * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 @file:Suppress("unused")
6
7 package kotlinx.coroutines.android
8
9 import android.os.*
10 import android.view.*
11 import androidx.annotation.*
12 import kotlinx.coroutines.*
13 import kotlinx.coroutines.internal.*
14 import java.lang.reflect.*
15 import kotlin.coroutines.*
16
17 /**
18 * Dispatches execution onto Android [Handler].
19 *
20 * This class provides type-safety and a point for future extensions.
21 */
22 public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay {
23 /**
24 * Returns dispatcher that executes coroutines immediately when it is already in the right context
25 * (current looper is the same as this handler's looper) without an additional [re-dispatch][CoroutineDispatcher.dispatch].
26 * This dispatcher does not use [Handler.post] when current looper is the same as looper of the handler.
27 *
28 * Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined].
29 * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation.
30 *
31 * Example of usage:
32 * ```
33 * suspend fun updateUiElement(val text: String) {
34 * /*
35 * * If it is known that updateUiElement can be invoked both from the Main thread and from other threads,
36 * * `immediate` dispatcher is used as a performance optimization to avoid unnecessary dispatch.
37 * *
38 * * In that case, when `updateUiElement` is invoked from the Main thread, `uiElement.text` will be
39 * * invoked immediately without any dispatching, otherwise, the `Dispatchers.Main` dispatch cycle via
40 * * `Handler.post` will be triggered.
41 * */
42 * withContext(Dispatchers.Main.immediate) {
43 * uiElement.text = text
44 * }
45 * // Do context-independent logic such as logging
46 * }
47 * ```
48 */
49 public abstract override val immediate: HandlerDispatcher
50 }
51
52 internal class AndroidDispatcherFactory : MainDispatcherFactory {
53
createDispatchernull54 override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
55 val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")
56 return HandlerContext(mainLooper.asHandler(async = true))
57 }
58
hintOnErrornull59 override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
60
61 override val loadPriority: Int
62 get() = Int.MAX_VALUE / 2
63 }
64
65 /**
66 * Represents an arbitrary [Handler] as an implementation of [CoroutineDispatcher]
67 * with an optional [name] for nicer debugging
68 *
69 * ## Rejected execution
70 *
71 * If the underlying handler is closed and its message-scheduling methods start to return `false` on
72 * an attempt to submit a continuation task to the resulting dispatcher,
73 * then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the
74 * [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete.
75 */
76 @JvmName("from") // this is for a nice Java API, see issue #255
77 @JvmOverloads
78 public fun Handler.asCoroutineDispatcher(name: String? = null): HandlerDispatcher =
79 HandlerContext(this, name)
80
81 private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android
82
83 @VisibleForTesting
84 internal fun Looper.asHandler(async: Boolean): Handler {
85 // Async support was added in API 16.
86 if (!async || Build.VERSION.SDK_INT < 16) {
87 return Handler(this)
88 }
89
90 if (Build.VERSION.SDK_INT >= 28) {
91 // TODO compile against API 28 so this can be invoked without reflection.
92 val factoryMethod = Handler::class.java.getDeclaredMethod("createAsync", Looper::class.java)
93 return factoryMethod.invoke(null, this) as Handler
94 }
95
96 val constructor: Constructor<Handler>
97 try {
98 constructor = Handler::class.java.getDeclaredConstructor(Looper::class.java,
99 Handler.Callback::class.java, Boolean::class.javaPrimitiveType)
100 } catch (ignored: NoSuchMethodException) {
101 // Hidden constructor absent. Fall back to non-async constructor.
102 return Handler(this)
103 }
104 return constructor.newInstance(this, null, true)
105 }
106
107 @JvmField
108 @Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN)
<lambda>null109 internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true)) }.getOrNull()
110
111 /**
112 * Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
113 */
114 internal class HandlerContext private constructor(
115 private val handler: Handler,
116 private val name: String?,
117 private val invokeImmediately: Boolean
118 ) : HandlerDispatcher(), Delay {
119 /**
120 * Creates [CoroutineDispatcher] for the given Android [handler].
121 *
122 * @param handler a handler.
123 * @param name an optional name for debugging.
124 */
125 constructor(
126 handler: Handler,
127 name: String? = null
128 ) : this(handler, name, false)
129
130 @Volatile
131 private var _immediate: HandlerContext? = if (invokeImmediately) this else null
132
133 override val immediate: HandlerContext = _immediate ?:
<lambda>null134 HandlerContext(handler, name, true).also { _immediate = it }
135
isDispatchNeedednull136 override fun isDispatchNeeded(context: CoroutineContext): Boolean {
137 return !invokeImmediately || Looper.myLooper() != handler.looper
138 }
139
dispatchnull140 override fun dispatch(context: CoroutineContext, block: Runnable) {
141 if (!handler.post(block)) {
142 cancelOnRejection(context, block)
143 }
144 }
145
scheduleResumeAfterDelaynull146 override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
147 val block = Runnable {
148 with(continuation) { resumeUndispatched(Unit) }
149 }
150 if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
151 continuation.invokeOnCancellation { handler.removeCallbacks(block) }
152 } else {
153 cancelOnRejection(continuation.context, block)
154 }
155 }
156
invokeOnTimeoutnull157 override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
158 if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
159 return DisposableHandle { handler.removeCallbacks(block) }
160 }
161 cancelOnRejection(context, block)
162 return NonDisposableHandle
163 }
164
cancelOnRejectionnull165 private fun cancelOnRejection(context: CoroutineContext, block: Runnable) {
166 context.cancel(CancellationException("The task was rejected, the handler underlying the dispatcher '${toString()}' was closed"))
167 Dispatchers.IO.dispatch(context, block)
168 }
169
<lambda>null170 override fun toString(): String = toStringInternalImpl() ?: run {
171 val str = name ?: handler.toString()
172 if (invokeImmediately) "$str.immediate" else str
173 }
174
equalsnull175 override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler
176 override fun hashCode(): Int = System.identityHashCode(handler)
177 }
178
179 @Volatile
180 private var choreographer: Choreographer? = null
181
182 /**
183 * Awaits the next animation frame and returns frame time in nanoseconds.
184 */
185 public suspend fun awaitFrame(): Long {
186 // fast path when choreographer is already known
187 val choreographer = choreographer
188 if (choreographer != null) {
189 return suspendCancellableCoroutine { cont ->
190 postFrameCallback(choreographer, cont)
191 }
192 }
193 // post into looper thread to figure it out
194 return suspendCancellableCoroutine { cont ->
195 Dispatchers.Main.dispatch(EmptyCoroutineContext, Runnable {
196 updateChoreographerAndPostFrameCallback(cont)
197 })
198 }
199 }
200
updateChoreographerAndPostFrameCallbacknull201 private fun updateChoreographerAndPostFrameCallback(cont: CancellableContinuation<Long>) {
202 val choreographer = choreographer ?:
203 Choreographer.getInstance()!!.also { choreographer = it }
204 postFrameCallback(choreographer, cont)
205 }
206
postFrameCallbacknull207 private fun postFrameCallback(choreographer: Choreographer, cont: CancellableContinuation<Long>) {
208 choreographer.postFrameCallback { nanos ->
209 with(cont) { Dispatchers.Main.resumeUndispatched(nanos) }
210 }
211 }
212