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