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