All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.kotlinx.kotlinx-coroutines-android.1.9.0-RC.2.source-code.HandlerDispatcher.kt Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
@file:Suppress("unused")

package kotlinx.coroutines.android

import android.os.*
import android.view.*
import androidx.annotation.*
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): MainCoroutineDispatcher {
        val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")
        return HandlerContext(mainLooper.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 an implementation of [CoroutineDispatcher]
 * with an optional [name] for nicer debugging
 *
 * ## Rejected execution
 *
 * If the underlying handler is closed and its message-scheduling methods start to return `false` on
 * an attempt to submit a continuation task to the resulting dispatcher,
 * then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the
 * [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete.
 */
@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
    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.
     */
    constructor(
        handler: Handler,
        name: String? = null
    ) : this(handler, name, false)

    override val immediate: HandlerContext = if (invokeImmediately) this else
        HandlerContext(handler, name, true)

    override fun isDispatchNeeded(context: CoroutineContext): Boolean {
        return !invokeImmediately || Looper.myLooper() != handler.looper
    }

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        if (!handler.post(block)) {
            cancelOnRejection(context, block)
        }
    }

    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) {
        val block = Runnable {
            with(continuation) { resumeUndispatched(Unit) }
        }
        if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
            continuation.invokeOnCancellation { handler.removeCallbacks(block) }
        } else {
            cancelOnRejection(continuation.context, block)
        }
    }

    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
        if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
            return DisposableHandle { handler.removeCallbacks(block) }
        }
        cancelOnRejection(context, block)
        return NonDisposableHandle
    }

    private fun cancelOnRejection(context: CoroutineContext, block: Runnable) {
        context.cancel(CancellationException("The task was rejected, the handler underlying the dispatcher '${toString()}' was closed"))
        Dispatchers.IO.dispatch(context, 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 && other.invokeImmediately == invokeImmediately
    // inlining `Boolean.hashCode()` for Android compatibility, as requested by Animal Sniffer
    override fun hashCode(): Int = System.identityHashCode(handler) xor if (invokeImmediately) 1231 else 1237
}

@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
    return if (choreographer != null) {
        suspendCancellableCoroutine { cont ->
            postFrameCallback(choreographer, cont)
        }
    } else {
        awaitFrameSlowPath()
    }
}

private suspend fun awaitFrameSlowPath(): Long = suspendCancellableCoroutine { cont ->
    if (Looper.myLooper() === Looper.getMainLooper()) { // Check if we are already in the main looper thread
        updateChoreographerAndPostFrameCallback(cont)
    } else { // post into looper thread to figure it out
        Dispatchers.Main.dispatch(cont.context, Runnable {
            updateChoreographerAndPostFrameCallback(cont)
        })
    }
}

private fun updateChoreographerAndPostFrameCallback(cont: CancellableContinuation) {
    val choreographer = choreographer ?: Choreographer.getInstance()!!.also { choreographer = it }
    postFrameCallback(choreographer, cont)
}

private fun postFrameCallback(choreographer: Choreographer, cont: CancellableContinuation) {
    choreographer.postFrameCallback { nanos ->
        with(cont) { Dispatchers.Main.resumeUndispatched(nanos) }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy