Platform.kt
package com.shengzhiai.yugu.internal
import android.os.Handler
import android.os.Looper
import okhttp3.OkHttpClient
import java.util.ArrayDeque
import java.util.concurrent.Executor
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.RejectedExecutionException
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.SynchronousQueue
import java.util.concurrent.ThreadFactory
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
/**
* Process-wide resources shared by every client, so creating and closing clients repeatedly does
* not accumulate threads or connection pools. Idle threads time out.
*/
internal object Shared {
private fun factory(prefix: String): ThreadFactory {
val n = AtomicInteger()
return ThreadFactory { r ->
Thread(r, prefix + "-" + n.incrementAndGet()).apply { isDaemon = true }
}
}
/** Timers: reconnect delays, handshake and result timeouts. */
val scheduler: ScheduledThreadPoolExecutor by lazy {
ScheduledThreadPoolExecutor(1, factory("yugu-scheduler")).apply {
removeOnCancelPolicy = true
setKeepAliveTime(15, TimeUnit.SECONDS)
allowCoreThreadTimeOut(true)
}
}
/** Fallback callback thread when there is no Android main looper (plain JVM). */
val callbackExecutor: ThreadPoolExecutor by lazy {
ThreadPoolExecutor(1, 1, 15, TimeUnit.SECONDS, LinkedBlockingQueue(), factory("yugu-callback")).apply {
allowCoreThreadTimeOut(true)
}
}
/** Worker threads of the async API. */
val asyncExecutor: ThreadPoolExecutor by lazy {
ThreadPoolExecutor(0, Int.MAX_VALUE, 30, TimeUnit.SECONDS, SynchronousQueue(), factory("yugu-async"))
}
/** Base OkHttp client; each YuguClient derives its own settings with newBuilder(). */
val okHttp: OkHttpClient by lazy { OkHttpClient() }
}
/** Android specifics, guarded so that plain JVM code paths never touch a stub. */
internal object Platform {
/** Main thread executor on Android, null on a plain JVM. */
fun mainExecutorOrNull(): Executor? = try {
if (Looper.getMainLooper() != null) MainThreadExecutor else null
} catch (t: Throwable) {
null
}
fun isMainThread(): Boolean = try {
val main = Looper.getMainLooper()
main != null && Looper.myLooper() == main
} catch (t: Throwable) {
false
}
fun defaultCallbackExecutor(): Executor = mainExecutorOrNull() ?: Shared.callbackExecutor
}
/** Posts to the Android main looper. */
internal object MainThreadExecutor : Executor {
private val handler by lazy { Handler(Looper.getMainLooper()) }
override fun execute(command: Runnable) {
if (!handler.post(command)) throw RejectedExecutionException("main looper is quitting")
}
}
/**
* Runs tasks one at a time, in submission order, on [target]. Callbacks of one session or one
* recorder never run concurrently. When [target] rejects a task the shared fallback runs it.
*/
internal class SerialExecutor(private val target: Executor) : Executor {
private val tasks = ArrayDeque<Runnable>()
private var active: Runnable? = null
override fun execute(command: Runnable) {
synchronized(this) {
tasks.add(Runnable {
try {
command.run()
} finally {
scheduleNext()
}
})
if (active == null) scheduleNext()
}
}
private fun scheduleNext() {
synchronized(this) {
val next = tasks.poll()
active = next
if (next != null) {
try {
target.execute(next)
} catch (e: RejectedExecutionException) {
Shared.callbackExecutor.execute(next)
}
}
}
}
}