-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathHandlerDispatcher.kt
176 lines (150 loc) · 5.97 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
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused")
package kotlinx.coroutines.experimental.android
import android.os.*
import android.support.annotation.VisibleForTesting
import android.view.*
import kotlinx.coroutines.experimental.*
import java.lang.reflect.Constructor
import java.util.concurrent.*
import kotlin.coroutines.experimental.*
/**
* Dispatches execution onto Android main thread and provides native [delay][Delay.delay] support.
*/
public val Dispatchers.Main: HandlerDispatcher
get() = mainDispatcher
/**
* Dispatches execution onto Android [Handler].
*
* This class provides type-safety and a point for future extensions.
*/
public sealed class HandlerDispatcher : CoroutineDispatcher(), Delay {
/**
* Returns dispatcher that executes coroutines immediately when it is already in the right handler context
* (current looper is the same as this handler's looper). See [isDispatchNeeded] documentation on
* why this should not be done by default.
*/
public abstract val immediate: HandlerDispatcher
}
/**
* Represents an arbitrary [Handler] as a implementation of [CoroutineDispatcher].
*/
public fun Handler.asCoroutineDispatcher(): HandlerDispatcher =
HandlerContext(this)
private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android
private val mainHandler = Looper.getMainLooper().asHandler(async = true)
@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)
}
private val mainDispatcher = HandlerContext(mainHandler, "Main")
/**
* Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
* @suppress **Deprecated**: Use [HandlerDispatcher].
*/
@Deprecated(
message = "Use HandlerDispatcher",
replaceWith = ReplaceWith("HandlerDispatcher",
imports = ["kotlinx.coroutines.experimental.android.HandlerDispatcher"])
)
public 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(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
handler.postDelayed({
with(continuation) { resumeUndispatched(Unit) }
}, unit.toMillis(time).coerceAtMost(MAX_DELAY))
}
override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle {
handler.postDelayed(block, unit.toMillis(time).coerceAtMost(MAX_DELAY))
return object : DisposableHandle {
override fun dispose() {
handler.removeCallbacks(block)
}
}
}
/**
* Awaits the next animation frame and returns frame time in nanoseconds.
* @suppress **Deprecated**: Use top-level [awaitFrame].
*/
@Deprecated(
message = "Use top-level awaitFrame",
replaceWith = ReplaceWith("kotlinx.coroutines.experimental.android.awaitFrame()")
)
public suspend fun awaitFrame(): Long =
kotlinx.coroutines.experimental.android.awaitFrame()
override fun toString() = name ?: handler.toString()
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 ->
mainHandler.post {
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) { mainDispatcher.resumeUndispatched(nanos) }
}
}