在 Android / Kotlin 项目开发中,定时任务很常见:
-
定时刷新 UI
-
轮询状态(如硬件、机器人、网络心跳)
-
延迟执行任务
-
任务节流 / 防抖
传统写法通常是用 Timer / TimerTask ,或者 ScheduledExecutorService。
但随着 Kotlin 和协程成为主流,这些旧方案越来越"不顺手"。
❓ Timer 明明能用,为什么要换协程?
一句话:
Timer 是线程级调度器,而协程是任务级调度模型。
🔥 老方案:Timer 带来的问题
代码示例(传统 Timer)
java
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) |
✅ 打造模块级协程作用域
用于管理模块内所有协程,支持自动取消与异常隔离。
Kotlin
/**
* 模块专用协程作用域
*/
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)
Kotlin
private var job: Job? = null
fun startPolling() {
if (job?.isActive == true) return
job = moduleScope.launch(CoroutineName("PollingTask")) {
val period = 500L
while (isActive) {
try {
doWork()
} catch (e: Exception) {
Log.e("Polling", "error: $e")
}
delay(period) // ✅ 定时间隔,不阻塞线程
}
}
}
fun stopPolling() {
job?.cancel() // ✅ 一键取消
}
固定频率轮询(避免 Timer 的堆积/漂移问题)
完全替代
Timer.scheduleAtFixedRate
Kotlin
job = moduleScope.launch(CoroutineName("FixedRateTask")) {
val period = 500L
var next = SystemClock.elapsedRealtime()
while (isActive) {
doWork()
next += period
delay((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 是线程级调度器,协程是任务级调度模型。