Worker生命周期与线程:后台任务的核心机制
说实话,Worker的生命周期和线程模型,是很多Android开发者容易忽略但又极其重要的部分。我记得刚接触WorkManager那会儿,以为它就是个简单的任务队列,直到线上出现了一个诡异的ANR——一个Worker跑了太久,把主线程给卡住了。嗯,从那以后,我对Worker的线程模型就格外上心。
Worker运行在后台线程
先明确一点:Worker默认运行在后台线程。WorkManager内部维护了一个线程池,你的doWork()方法就在这个线程池里执行。这意味着你不需要手动开线程,也不需要担心阻塞主线程。
核心要点:Worker的doWork()方法运行在WorkManager的默认后台线程池中,线程优先级为THREAD_PRIORITY_BACKGROUND。
但这里有个坑——Worker本身不是线程安全的。如果你在Worker里访问了共享的可变状态,比如一个静态变量或者单例对象,那就要小心了。我遇到过一个问题:两个Worker同时修改同一个静态Map,结果数据全乱了。解决方案很简单:要么加锁,要么用不可变数据结构。
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// 这里已经在后台线程了
// 可以放心做耗时操作
val result = fetchDataFromNetwork()
saveToDatabase(result)
return Result.success()
}
}
你可能会问:如果doWork()里抛异常了怎么办?Worker会捕获异常,然后返回Result.failure()。但注意,未捕获的异常会导致Worker被销毁,任务状态变成失败。所以,我习惯在doWork()里加个try-catch,把异常信息记录下来,方便排查问题。
注意:不要在doWork()里调用Thread.sleep()或者无限循环。Worker有10分钟的超时限制(Android 12+),超时后会被强制停止,返回Result.failure()。
CoroutineWorker:协程时代的首选
如果你项目里用了Kotlin协程,那CoroutineWorker就是你的最佳选择。它比普通Worker更轻量,而且天然支持协程的取消和结构化并发。
我个人的习惯是:新项目一律用CoroutineWorker。为什么?因为协程的挂起函数可以让你写出更简洁的异步代码,而且不需要手动管理线程池。
class MyCoroutineWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// 这里已经是协程上下文了
// 可以调用任何挂起函数
val data = fetchDataFromApi() // 挂起函数
processData(data) // 普通函数
return Result.success()
}
}
CoroutineWorker的doWork()是一个挂起函数,它运行在Dispatchers.Default上。这意味着你可以直接调用其他挂起函数,而不需要手动切换线程。而且,当Worker被取消时,协程会自动取消,不会留下僵尸任务。
小技巧:如果你需要在CoroutineWorker里切换线程,可以用withContext(Dispatchers.IO)来做IO操作。但记住,不要用Dispatchers.Main,因为Worker本身就不该碰UI。
我记得有一次,一个同事用CoroutineWorker做图片上传,结果发现上传进度回调没反应。排查了半天,发现他在doWork()里用了GlobalScope.launch。嗯,这是个经典错误——在CoroutineWorker里不要用GlobalScope,应该用this(即CoroutineScope)来启动协程,这样才能被正确取消。
RxWorker:响应式编程的集成方案
如果你的项目重度使用RxJava,那RxWorker就是为你准备的。它把Worker的生命周期和RxJava的Observable/Flowable绑定在一起,支持背压和操作符链。
class MyRxWorker(context: Context, params: WorkerParameters) :
RxWorker(context, params) {
override fun createWork(): Single<Result> {
return fetchDataFromApi()
.map { data -> processData(data) }
.map { Result.success() }
.onErrorReturn { Result.failure() }
}
}
RxWorker的createWork()方法返回一个Single<Result>。当这个Single被订阅时,Worker开始执行;当Single完成或出错时,Worker结束。而且,RxWorker会自动处理取消——如果Worker被取消,Single的订阅会被自动销毁。
不过说实话,现在新项目用RxJava的越来越少了。我个人建议:除非你维护的是老项目,否则优先考虑CoroutineWorker。RxWorker的优点是操作符丰富,但缺点是学习曲线陡峭,而且容易写出内存泄漏的代码。
对比总结:
| 类型 | 线程模型 | 取消支持 | 适用场景 |
|---|---|---|---|
| Worker | 默认后台线程池 | 手动检查isStopped | 简单任务,不需要协程/Rx |
| CoroutineWorker | Dispatchers.Default | 自动取消协程 | 新项目,协程项目 |
| RxWorker | RxJava调度器 | 自动取消订阅 | 老项目,RxJava项目 |
自定义线程池与Worker
有时候,默认的线程池满足不了你的需求。比如,你想限制并发数,或者给任务设置优先级。这时候就需要自定义线程池了。
WorkManager允许你通过Configuration来设置自定义的Executor。我曾在项目中遇到过一个问题:多个Worker同时下载大文件,导致内存飙升。后来我自定义了一个线程池,把最大并发数限制在3个,问题就解决了。
// 自定义线程池
val customExecutor = ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
30, TimeUnit.SECONDS, // 空闲线程存活时间
LinkedBlockingQueue<Runnable>(), // 任务队列
ThreadFactory { runnable ->
Thread(runnable, "work-manager-custom-thread").apply {
priority = Process.THREAD_PRIORITY_BACKGROUND
}
}
)
// 初始化WorkManager时传入
val config = Configuration.Builder()
.setExecutor(customExecutor)
.build()
WorkManager.initialize(context, config)
这里要注意:自定义线程池会影响所有Worker,不只是你当前的这个。所以,如果你只想给某个特定Worker设置线程池,那就不太合适了。更好的做法是:在Worker内部自己管理线程。
比如,在CoroutineWorker里,你可以用withContext切换到自定义的Dispatcher:
class CustomDispatcherWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
private val customDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
override suspend fun doWork(): Result {
return withContext(customDispatcher) {
// 在自定义线程池中执行
doHeavyWork()
Result.success()
}
}
override fun onStopped() {
super.onStopped()
// 记得关闭自定义Dispatcher
customDispatcher.close()
}
}
警告:自定义线程池一定要记得关闭,否则会导致内存泄漏。在Worker的onStopped()或finalize()里关闭是个好习惯。
我曾经犯过一个错误:在Worker里创建了一个线程池,但忘了关闭。结果每次执行Worker都新建一个线程池,最后线程数暴涨,应用直接OOM了。嗯,从那以后,我每次创建线程池都会在注释里写上「记得关闭」。
Worker生命周期图解
下面这张图展示了Worker从创建到销毁的完整生命周期,以及线程模型的关系:
从这张图可以看出,Worker的生命周期其实很简单:入队→运行→结束(成功/失败/取消)。但线程模型的选择会影响你在每个阶段能做什么。比如,在RUNNING阶段,普通Worker需要手动检查isStopped,而CoroutineWorker和RxWorker会自动处理取消。
最后,我想说一句:没有银弹。选择哪种Worker,取决于你的项目现状和团队技术栈。但不管选哪种,理解它的线程模型和生命周期,是写出稳定后台任务的前提。嗯,希望这些经验能帮你少踩几个坑。