Kotlin 协程最佳实践:用 CoroutineScope + SupervisorJob 替代 Timer,实现优雅周期任务调度
在 Android / Kotlin 项目开发中,定时任务很常见:
定时刷新 UI
轮询状态(如硬件、机器人、网络心跳)
延迟执行任务
任务节流 / 防抖
传统写法通常是用 Timer / TimerTask,或者 ScheduledExecutorService。
但随着 Kotlin 和协程成为主流,这些旧方案越来越“不顺手”。
❓ Timer 明明能用,为什么要换协程?
一句话:
Timer 是线程级调度器,而协程是任务级调度模型。
🔥 老方案:Timer 带来的问题
代码示例(传统 Timer)
Timer timer = new Timer();
TimerTask task = new TimerTask() {@Override public void run() { doWork(); }
};
timer.scheduleAtFixedRate(task, 0, 500);// 停止
task.cancel();
timer.cancel();
timer.purge();
❌ Timer 的核心缺点
| 缺点 | 说明 |
|---|---|
| ❌ 创建新线程 | Timer 通常为每个 Timer 开一个新线程,资源浪费 |
| ❌ 异常终止整个 Timer | 任意 TimerTask.run() 未捕获异常 → 整个 Timer 线程挂掉 |
| ❌ 手动管理生命周期 | 要 task.cancel() + timer.cancel() + purge() |
| ❌ 代码繁琐 | 回调式、不符合 Kotlin Lambda 风格 |
| ❌ 可读性差 | 无法与 suspend / Flow / Structured concurrency 一起工作 |
特别是这一点:
只要一个任务抛异常,整个 Timer 会停止,后续任务不再执行。
这在生产环境是严重风险。
✅ 新方案:协程(Coroutine)更现代、更安全、更可控
我们直接用:
CoroutineScope + SupervisorJob + while(isActive) + delay()
✅ 协程优势
| 优点 | 说明 |
|---|---|
| ✅ 轻量级 | 协程本质是任务,复用线程池,不额外创建线程 |
| ✅ SupervisorJob | 子协程异常不影响兄弟任务(不会“一倒全倒”) |
| ✅ 自动生命周期管理 | Scope.cancel() 一行释放所有协程 |
| ✅ 简洁 | while (isActive) + delay() 即可完成周期任务 |
| ✅ 可调试 | CoroutineName + Coroutine Debugger 面板 |
| ✅ 符合 Kotlin 风格 | 支持 suspend、Flow、withContext(IO/Default) |
✅ 打造模块级协程作用域
用于管理模块内所有协程,支持自动取消与异常隔离。
/*** 模块专用协程作用域*/
private val moduleScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("HomeModule")
)
解释:
| 组件 | 意义 |
|---|---|
SupervisorJob() | 子协程异常不影响父协程与兄弟协程 |
Dispatchers.Default | CPU 密集型任务的后台线程池,不是主线程 |
CoroutineName("HomeModule") | 调试时能看到协程名字,方便定位 |
开启调试:
System.setProperty("kotlinx.coroutines.debug", "on")
log 输出示例:
DefaultDispatcher-worker-2 @HomeModule/PollingTask#7
非常方便定位。
周期任务:协程方案(替代 Timer)
private var job: Job? = nullfun startPolling() {if (job?.isActive == true) returnjob = moduleScope.launch(CoroutineName("PollingTask")) {val period = 500Lwhile (isActive) {try {doWork()} catch (e: Exception) {Log.e("Polling", "error: $e")}delay(period) // ✅ 定时间隔,不阻塞线程}}
}fun stopPolling() {job?.cancel() // ✅ 一键取消
}
固定频率轮询(避免 Timer 的堆积/漂移问题)
完全替代
Timer.scheduleAtFixedRate
job = moduleScope.launch(CoroutineName("FixedRateTask")) {val period = 500Lvar next = SystemClock.elapsedRealtime()while (isActive) {doWork()next += perioddelay((next - SystemClock.elapsedRealtime()).coerceAtLeast(0L))}
}
优势:
- 不会因为执行时间长而连续堆积执行
- 不会受系统时间变动影响(使用
elapsedRealtime)
选型建议(重点)
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| Android / Kotlin 项目中周期任务、UI 配合 | ✅ 协程(Coroutine) | 轻量、安全、可控 |
| 多任务并行,要求互不影响 | ✅ SupervisorJob + CoroutineScope | 子任务异常不影响全局 |
| 真正需要线程级调度池(非 Kotlin 项目 / 背景任务) | ⚠️ ScheduledExecutorService | 比 Timer 更安全、更可控 |
| 老方案 | ❌ Timer / TimerTask | 异常会终止 Timer,生命周期难管理 |
✅ 如果项目已经在用 Kotlin/协程,优先用协程。
⚠️ 如果需要“线程级定时调度池”,ScheduledExecutorService比 Timer 更好。
🔥 但在 Android 开发中,仍然建议用协程包装调度逻辑。
最终总结(金句)
协程不是为了替代 Timer,而是为了替代 Timer 的所有问题。
协程 = 轻量任务模型 + 生命周期管理 + 异常隔离 + 自带取消机制。
一句话记住:
Timer 是线程级调度器,协程是任务级调度模型。
