Handler与协程/Executor 的对比与互操作

46 阅读2分钟

一、定位与区别(一句话印象)

  • Handler/Looper单线程串行队列;确定执行线程(哪个 Looper 就在哪个线程跑)。适合UI 主线程专用串行后台
  • Executor线程池抽象,可并发执行;适合CPU/阻塞 I/O 并发
  • 协程(Coroutine) :更高层的调度+取消+结构化并发;跑在某个 Dispatcher ⟶ 线程/线程池/Looper 上。Dispatchers.Main 本质就是主线程 Looper
维度HandlerExecutor协程
并发模型单线程串行线程池并发由 Dispatcher 决定(可串行/并行)
取消/超时手动 removeCallbacksFuture.cancel()(常见但不统一)一等公民:cancel/withTimeout,可级联
错误传播自己捕获交给提交者/UncaughtExceptionHandler结构化并发、作用域内统一处理
场景切主线程、UI 时序CPU/I/O 并发绝大多数异步,易组合

二、互操作(互转最常用写法)

1) Handler ↔ 协程

// Handler ➜ Dispatcher(把某个 Looper 变成协程调度器)
val mainHandler = Handler(Looper.getMainLooper())
val mainDispatcher = mainHandler.asCoroutineDispatcher()

// 切到主线程(协程方式)
withContext(Dispatchers.Main) { /* 更新UI */ }

// 用 lifecycleScope 代替 post(生命周期安全)
lifecycleScope.launch { /* Main by default in AndroidX */ }

取消联动(重要) :协程取消时移除已投递的 Runnable

suspend fun postOnMain(block: () -> Unit) = suspendCancellableCoroutine<Unit> { cont ->
    val h = Handler(Looper.getMainLooper())
    val r = Runnable { if (cont.isActive) { block(); cont.resume(Unit) {} } }
    h.post(r)
    cont.invokeOnCancellation { h.removeCallbacks(r) }
}

2) Executor ↔ 协程

// 线程池 ➜ Dispatcher
val cpuPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
val cpuDispatcher = cpuPool.asCoroutineDispatcher()

// 使用并注意关闭(否则泄漏线程)
try {
    withContext(cpuDispatcher) { heavyCompute() }
} finally {
    cpuDispatcher.close()   // 也可在 onDestroy/onCleared 里关
}

3) Handler ↔ Executor

// Handler ➜ Executor(投到指定 Looper)
val mainExec: Executor = Executor { r -> mainHandler.post(r) }

// 系统已有:ContextCompat.getMainExecutor(context)

// Executor ➜ Handler(较少见,通常没必要)
val single = Executors.newSingleThreadExecutor()
val bgHandler = HandlerThread("bg").apply { start() }.let { Handler(it.looper) }
val execFromHandler: Executor = Executor { r -> bgHandler.post(r) }

三、选择建议(什么时候该用谁)

  • 切主线程/严格串行时序:Handler(Looper.getMainLooper()) 或 withContext(Dispatchers.Main);新代码优先协程(lifecycleScope)。

  • 大量并发任务:Executor 或直接协程 Dispatchers.IO/Default;协程更好管理取消/错误。

  • 专用串行后台(如写文件、打点):

    • 纯 Handler:HandlerThread + Handler;
    • 协程:HandlerThread.looper.asCoroutineDispatcher() 得到一个串行 Dispatcher

四、常见细节与最佳实践

  • Main.immediate:withContext(Dispatchers.Main.immediate) 若已在主线程,同步执行,减少一次切换;行为类似同步的 Handler 调度。
  • 避免泄漏:Executor.asCoroutineDispatcher() 记得 close();HandlerThread 记得 quitSafely()+join();页面/VM 销毁时 removeCallbacksAndMessages(null) 或取消协程作用域。
  • 取消语义:协程取消不会自动取消你自己 postDelayed 的任务,务必在 invokeOnCancellation 里 removeCallbacks(上面的示例已给)。
  • 优先用协程封装旧回调:suspendCancellableCoroutine/callbackFlow 把基于 Handler 的 API 包成挂起或流式,统一到 Dispatchers 上。

五、综合示例:IO 并发 + 切主线程更新 UI

class VM : ViewModel() {
    private val io = Executors.newCachedThreadPool().asCoroutineDispatcher()

    override fun onCleared() { io.close() }

    fun loadAndShow() = viewModelScope.launch {
        val data = withContext(io) { repo.fetch() }   // 并发线程池
        withContext(Dispatchers.Main) {               // 切回主线程
            render(data)
        }
    }
}

结论

  • Handler/Looper:精准绑定线程、控制 UI/串行时序。
  • Executor:并发执行载体。
  • 协程:把二者统一到一个可取消、可组合、生命周期友好的模型里;用 Dispatchers.Main/IO/Default + asCoroutineDispatcher() 让它们顺畅互通。