智能的闲时监测机制

185 阅读2分钟

如何检查线程空闲

在Android冷启动和某些业务场景中,我们期望在主线程不繁忙的情况下请求Api或者初始化SDK等。借助这个策略我们可以很智能的完成一些并不是很重要的任务。很多人马上就想到了MessageQueue.addIdleHandler

addIdleHandler

Main线程主要用于UI绘制相关任务,Android提供Looper和MessageQueue来管理这些任务,同时提供了会在UI任务不繁忙的回调接口IdleHandler。那么能否给子线程也用呢,不可以的,子线程执行的任务不依赖Looper。

public static interface IdleHandler {
    /**
     * 当MessageQueue当前没有任务处理时候会回调,而且huidiao运行在主线程
     * return true会不断持续监测和回调
     * return false 会remove这个回调,调用一次
     */
    boolean queueIdle();
}

协程和IdleHandler

知道了api用法我们很容易写出如下代码

Looper.getMainLooper().queue.addIdleHandler { 
    // 执行延迟的任务
    false
}

当我们有需求例如跟随生命周期销毁,或者添加超时机制那么实现就很麻烦了。但是如果我们借助协程,就会非常简单。首先我们借助suspendCancellableCoroutine实现一个挂起函数, 调用invokeOnCancellation在cancel时候remove回调,使用带有生命周期的协程作用域会在销毁时remove回调。

import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

suspend fun waitForIdle() = suspendCancellableCoroutine {
    val queue = Looper.getMainLooper().queue
    val callback = {
        it.resume(Unit);false
    }
    queue.addIdleHandler(callback)
    it.invokeOnCancellation {
        queue.removeIdleHandler(callback)
    }
}

测试延迟执行效果, 主线程的挂起并不是阻塞主线程所以不会ANR:

viewModelScope.launch {
    // start time
    waitForIdle()
    requestRedDot()
    //end Log.d("requestRedDot", "after: $time")
}
// requestRedDot - after: 310ms

超时机制 withTimeout

由于这个回调是在主线程不繁忙时候才会回调,我们并不知道它的时间,如果我们有需求最长等待5秒即刻执行任务,那么我们可以借助withTimeout系列函数。

val t: T? = withTimeoutOrNull(5.seconds) { // T} //5秒后返回null
val t: T = withTimeout(5.seconds) { // T} //5秒后抛出异常

我们需要等待5秒,如果没有执行到回调立即解除addIdleHandler让挂起函数向后执行。

suspend fun awaitIdle(timeout: Duration = 5.seconds) = withTimeoutOrNull(timeout) {
    waitForIdle()
}

测试下注释掉如下代码,来模拟长时间没有回调

...
    val callback = {
        //it.resume(Unit);false
    }

// requestRedDot - after: 5000ms

合并

import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

suspend fun awaitIdle(timeout: Duration = 5.seconds) = withTimeoutOrNull(timeout) {
    suspendCancellableCoroutine {
        val queue = Looper.getMainLooper().queue
        val callback = {
            it.resume(Unit);false
        }
        queue.addIdleHandler(callback)
        it.invokeOnCancellation {
            queue.removeIdleHandler(callback)
        }
    }
}