创建第一个WorkManager任务

好,到了动手的时候了。咱们要做的第一件事,就是写一个真正的Worker类,然后用OneTimeWorkRequest把它调度起来。别看这步简单,它是整个WorkManager体系的起点——你后面所有的复杂逻辑、依赖注入、Mock测试,都得先让这个最简单的任务跑通。

先聊聊Worker是个啥

Worker,直译就是“工人”。在WorkManager里,它代表一段具体的后台任务代码。你想想看,前台UI不负责干活,脏活累活全交给Worker在后台线程完成。我个人的习惯是,每个Worker只做一件事,职责单一,方便后续替换和测试。

Worker必须继承自androidx.work.Worker,然后重写doWork()方法。这个方法返回一个Result,要么成功,要么失败,要么重试。嗯,就三种情况,但实际项目中我踩过一个坑——没处理好重试策略,结果任务无限重试,把服务器打挂了。回头再细说。

提示:doWork()是在后台线程执行的,里面可以直接做网络请求、数据库读写等耗时操作。但要注意,系统给了你10分钟的超时限制,超过会被标记为失败。

编写一个最简单的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下执行”,就会等待条件满足再跑。

关键点:OneTimeWorkRequest只能执行一次。即使你多次enqueue同一个Request,系统也不会重复执行——它靠工作ID去重。如果需要重复执行,请使用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到调度执行的全流程,以及关键组件之间的关系。建议你把它存下来,后面学依赖注入时还能对照看。

WorkManager第一个任务核心流程 第一个WorkManager任务:定义 → 调度 → 执行 编写Worker类 继承androidx.work.Worker OneTimeWorkRequest.Builder 设置约束、标签、退避策略 enqueue提交 WorkManager.getInstance() doWork()执行 后台线程(默认10分钟超时) 网络/数据库/文件操作 返回 Result success() / failure() / retry() 决定任务生命周期 🔍 通过 getWorkInfoByIdLiveData 观测状态变化 ENQUEUED → RUNNING → SUCCEEDED / FAILED / CANCELLED 可附加监听

最后说几句

这一章的内容其实很简单,但它是后面所有高级特性的地基。我建议你立刻动手在自己的项目里写一个HelloWorker,然后用OneTimeWorkRequest跑起来。哪怕只是打印一行日志,也能让你对这个流程有肌肉记忆。下一章我们会给它加上输入输出数据,但这里就先到这儿吧。

注意:千万不要在生产环境的Worker里做密集计算或无限循环。WorkManager会被系统杀掉,而且你的任务可能被延迟执行。保持Worker轻量、幂等,这是最佳实践。