创建第一个WorkManager任务
好,到了动手的时候了。咱们要做的第一件事,就是写一个真正的Worker类,然后用OneTimeWorkRequest把它调度起来。别看这步简单,它是整个WorkManager体系的起点——你后面所有的复杂逻辑、依赖注入、Mock测试,都得先让这个最简单的任务跑通。
先聊聊Worker是个啥
Worker,直译就是“工人”。在WorkManager里,它代表一段具体的后台任务代码。你想想看,前台UI不负责干活,脏活累活全交给Worker在后台线程完成。我个人的习惯是,每个Worker只做一件事,职责单一,方便后续替换和测试。
Worker必须继承自androidx.work.Worker,然后重写doWork()方法。这个方法返回一个Result,要么成功,要么失败,要么重试。嗯,就三种情况,但实际项目中我踩过一个坑——没处理好重试策略,结果任务无限重试,把服务器打挂了。回头再细说。
编写一个最简单的Worker
我们来写一个范例:上传日志文件的Worker。假设你的App需要定期把本地日志上传到服务器,第一步先试试一次性上传。
// UploadLogWorker.kt
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
class UploadLogWorker(
appContext: Context,
workerParams: WorkerParameters
) : Worker(appContext, workerParams) {
companion object {
private const val TAG = "UploadLogWorker"
}
override fun doWork(): Result {
return try {
// 模拟上传操作
Log.d(TAG, "开始上传日志...")
Thread.sleep(2000) // 假装在干活
Log.d(TAG, "日志上传成功")
// 返回成功
Result.success()
} catch (e: Exception) {
Log.e(TAG, "上传失败", e)
// 返回失败,可设置重试
Result.retry()
}
}
}
这里有几个细节需要注意:
- 构造参数必须接收
Context和WorkerParameters,WorkManager会帮你自动注入。不能自己乱改。 - doWork()是同步方法,返回Result后Worker就结束了。如果你需要异步,请使用
CoroutineWorker或RxWorker,后面章节会讲。 - Result.retry()会触发WorkManager的默认重试策略,默认是退避算法,但你真的需要仔细配置,我曾经就因为没设置重试间隔,导致失败后立即重试,又失败又重试,CPU直接飙满。
用OneTimeWorkRequest调度它
Worker写好了,怎么让它跑起来?答案是创建WorkRequest。对于一次性任务,我们用OneTimeWorkRequest.Builder。看看代码:
// 在Activity或ViewModel中调度
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
val uploadRequest = OneTimeWorkRequest.Builder(UploadLogWorker::class.java)
.setConstraints(...) // 可选:设置约束(如网络可用)
.addTag("upload_log") // 可选:打标签方便管理
.build()
WorkManager.getInstance(context).enqueue(uploadRequest)
看到没?就这么几行。enqueue方法会把任务加入队列,WorkManager会选择合适的时机执行。默认情况下,它会尽快执行,但如果你设置了约束(Constraints),比如“只在Wi-Fi下执行”,就会等待条件满足再跑。
PeriodicWorkRequest,但那个更复杂些。再聊聊启动任务的两个方式
| 方式 | 适用场景 | 注意 |
|---|---|---|
| WorkManager.enqueue() | 标准后台执行,系统管理生命周期 | 即使App被杀,任务也能恢复 |
| 直接调用Worker的doWork() | 仅用于单元测试 | 生产环境不要这么做,会丢失所有WorkManager能力 |
我一开始学习时,总想偷懒直接new一个Worker实例去调doWork(),后来发现这样做根本拿不到InputData、Constraints那些机制。说白了,WorkManager的调度框架才是灵魂,Worker只是肉躯。
验证任务是否跑起来了
怎么知道Worker执行了?最简单的是看logcat。你也可以通过WorkManager的API查询工作状态:
val workManager = WorkManager.getInstance(context)
val workInfoLiveData = workManager.getWorkInfoByIdLiveData(uploadRequest.id)
workInfoLiveData.observe(this) { workInfo ->
if (workInfo != null) {
Log.d("WorkStatus", "状态: ${workInfo.state}")
// 可能的状态:ENQUEUED, RUNNING, SUCCEEDED, FAILED, CANCELLED
}
}
这个LiveData会实时推送工作状态变化。你想想看,这在调试时特别有用——能清楚看到任务从排队到执行到结束的全过程。不过我建议只在开发和测试阶段这么用,生产环境不要过度监听,以免内存泄漏。
避坑指南:我踩过的三个坑
第一个坑:忘记添加android:name=".WorkManagerInitializer"初始化配置。如果你在Application里手动初始化WorkManager,需要把默认的自动初始化关掉。否则会冲突。我曾因此浪费了半天时间。
第二个坑:Worker里持有Activity的Context引用。WorkManager传给Worker的context是ApplicationContext,这没问题。但如果你不小心在Worker里引用了Activity的Context,任务延迟执行时Activity已经销毁,就会内存泄漏甚至崩溃。切记。
第三个坑:Result.retry()没配置退避策略。默认退避是ExponentialBackoffPolicy,从10秒开始,但如果你不做任何限制,它会在短时间内指数增长,最高可达数小时。我建议至少设置一个最大重试次数:
val uploadRequest = OneTimeWorkRequest.Builder(UploadLogWorker::class.java)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
这里把最小退避时间设为MIN_BACKOFF_MILLIS(10秒),避免重试过于频繁。
一张图总结这一章的核心逻辑
下面这张SVG图展示了从定义Worker到调度执行的全流程,以及关键组件之间的关系。建议你把它存下来,后面学依赖注入时还能对照看。
最后说几句
这一章的内容其实很简单,但它是后面所有高级特性的地基。我建议你立刻动手在自己的项目里写一个HelloWorker,然后用OneTimeWorkRequest跑起来。哪怕只是打印一行日志,也能让你对这个流程有肌肉记忆。下一章我们会给它加上输入输出数据,但这里就先到这儿吧。