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