• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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