一、定位与区别(一句话印象)
- Handler/Looper:单线程串行队列;确定执行线程(哪个 Looper 就在哪个线程跑)。适合UI 主线程或专用串行后台。
- Executor:线程池抽象,可并发执行;适合CPU/阻塞 I/O 并发。
- 协程(Coroutine) :更高层的调度+取消+结构化并发;跑在某个 Dispatcher ⟶ 线程/线程池/Looper 上。Dispatchers.Main 本质就是主线程 Looper。
| 维度 | Handler | Executor | 协程 |
|---|---|---|---|
| 并发模型 | 单线程串行 | 线程池并发 | 由 Dispatcher 决定(可串行/并行) |
| 取消/超时 | 手动 removeCallbacks | Future.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() 让它们顺畅互通。