1 /*
<lambda>null2 * Copyright 2016-2020 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 androidx.annotation.*
11 import android.view.*
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>) =
55 HandlerContext(Looper.getMainLooper().asHandler(async = true))
56
57 override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
58
59 override val loadPriority: Int
60 get() = Int.MAX_VALUE / 2
61 }
62
63 /**
64 * Represents an arbitrary [Handler] as a implementation of [CoroutineDispatcher]
65 * with an optional [name] for nicer debugging
66 */
67 @JvmName("from") // this is for a nice Java API, see issue #255
68 @JvmOverloads
69 public fun Handler.asCoroutineDispatcher(name: String? = null): HandlerDispatcher =
70 HandlerContext(this, name)
71
72 private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android
73
74 @VisibleForTesting
75 internal fun Looper.asHandler(async: Boolean): Handler {
76 // Async support was added in API 16.
77 if (!async || Build.VERSION.SDK_INT < 16) {
78 return Handler(this)
79 }
80
81 if (Build.VERSION.SDK_INT >= 28) {
82 // TODO compile against API 28 so this can be invoked without reflection.
83 val factoryMethod = Handler::class.java.getDeclaredMethod("createAsync", Looper::class.java)
84 return factoryMethod.invoke(null, this) as Handler
85 }
86
87 val constructor: Constructor<Handler>
88 try {
89 constructor = Handler::class.java.getDeclaredConstructor(Looper::class.java,
90 Handler.Callback::class.java, Boolean::class.javaPrimitiveType)
91 } catch (ignored: NoSuchMethodException) {
92 // Hidden constructor absent. Fall back to non-async constructor.
93 return Handler(this)
94 }
95 return constructor.newInstance(this, null, true)
96 }
97
98 @JvmField
99 @Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN)
<lambda>null100 internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true)) }.getOrNull()
101
102 /**
103 * Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
104 */
105 internal class HandlerContext private constructor(
106 private val handler: Handler,
107 private val name: String?,
108 private val invokeImmediately: Boolean
109 ) : HandlerDispatcher(), Delay {
110 /**
111 * Creates [CoroutineDispatcher] for the given Android [handler].
112 *
113 * @param handler a handler.
114 * @param name an optional name for debugging.
115 */
116 public constructor(
117 handler: Handler,
118 name: String? = null
119 ) : this(handler, name, false)
120
121 @Volatile
122 private var _immediate: HandlerContext? = if (invokeImmediately) this else null
123
124 override val immediate: HandlerContext = _immediate ?:
<lambda>null125 HandlerContext(handler, name, true).also { _immediate = it }
126
isDispatchNeedednull127 override fun isDispatchNeeded(context: CoroutineContext): Boolean {
128 return !invokeImmediately || Looper.myLooper() != handler.looper
129 }
130
dispatchnull131 override fun dispatch(context: CoroutineContext, block: Runnable) {
132 handler.post(block)
133 }
134
scheduleResumeAfterDelaynull135 override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
136 val block = Runnable {
137 with(continuation) { resumeUndispatched(Unit) }
138 }
139 handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
140 continuation.invokeOnCancellation { handler.removeCallbacks(block) }
141 }
142
invokeOnTimeoutnull143 override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
144 handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
145 return object : DisposableHandle {
146 override fun dispose() {
147 handler.removeCallbacks(block)
148 }
149 }
150 }
151
<lambda>null152 override fun toString(): String = toStringInternalImpl() ?: run {
153 val str = name ?: handler.toString()
154 if (invokeImmediately) "$str.immediate" else str
155 }
156
equalsnull157 override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler
158 override fun hashCode(): Int = System.identityHashCode(handler)
159 }
160
161 @Volatile
162 private var choreographer: Choreographer? = null
163
164 /**
165 * Awaits the next animation frame and returns frame time in nanoseconds.
166 */
167 public suspend fun awaitFrame(): Long {
168 // fast path when choreographer is already known
169 val choreographer = choreographer
170 if (choreographer != null) {
171 return suspendCancellableCoroutine { cont ->
172 postFrameCallback(choreographer, cont)
173 }
174 }
175 // post into looper thread thread to figure it out
176 return suspendCancellableCoroutine { cont ->
177 Dispatchers.Main.dispatch(EmptyCoroutineContext, Runnable {
178 updateChoreographerAndPostFrameCallback(cont)
179 })
180 }
181 }
182
updateChoreographerAndPostFrameCallbacknull183 private fun updateChoreographerAndPostFrameCallback(cont: CancellableContinuation<Long>) {
184 val choreographer = choreographer ?:
185 Choreographer.getInstance()!!.also { choreographer = it }
186 postFrameCallback(choreographer, cont)
187 }
188
postFrameCallbacknull189 private fun postFrameCallback(choreographer: Choreographer, cont: CancellableContinuation<Long>) {
190 choreographer.postFrameCallback { nanos ->
191 with(cont) { Dispatchers.Main.resumeUndispatched(nanos) }
192 }
193 }
194