Android WorkManager 笔记

本文档整理了 Android Jetpack WorkManager 的几种典型使用方式:同步阻塞任务、协程挂起任务、Function 函数式接口,以及任务状态映射的实现思路。

适用场景:需要在应用退出或重启后仍可调度/执行后台任务(如日志上传、数据同步、定时备份等)。


一、简单的同步阻塞任务

说明Worker 是 WorkManager 最基础的实现方式,doWork()后台线程 中同步执行,方法返回时任务即结束。返回值 Result 表示成功 (success())、失败 (failure()) 或可重试 (retry())。由于运行在 Executor 上,不能 直接在 doWork() 里调用挂起函数(suspend),否则会阻塞线程池。

kotlin 复制代码
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.Result
​
class UploadWorker(
    appContext: Context,
    workerParams: WorkerParameters
) : Worker(appContext, workerParams) {
​
    override fun doWork(): Result {
        return try {
            // 执行耗时操作,例如上传文件
            val imageUrl = inputData.getString("image_url")
             uploadImage(imageUrl)
​
            // 返回成功
            Result.success()
        } catch (e: Exception) {
            // 返回失败,可根据配置重试
            Result.failure()
        }
    }
​
    private fun uploadImage(url: String?) {
​
    }
}

二、支持挂起函数

说明 :当业务里已经有大量 suspend 函数(例如网络请求、数据库读写)时,使用 CoroutineWorker 可以直接复用挂起代码而无需 runBlockingdoWork()suspend 函数,WorkManager 内部会在 Dispatchers.Default 上调度,不会阻塞主线程

kotlin 复制代码
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
​
class UploadCoroutineWorker(
    appContext: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
​
    // 直接调用挂起函数,无需担心线程切换
    override suspend fun doWork(): Result {
        return try {
            val imageUrl = inputData.getString("image_url")
 uploadImage(imageUrl)
​
​
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
private fun uploadImage(url: String?) {  
  
    }
​
​
}

三、函数式接口

说明 :最常见的「输入→输出」转换接口。下面的例子把 Integer 转成带前缀的 String

csharp 复制代码
public interface Function<I, O> {
    
    O apply(I input);
}

例子

typescript 复制代码
Function<Integer, String> intToString = new Function<Integer, String>() {
    @Override
    public String apply(Integer input) {
        return "Number is: " + input;
    }
};
​
// 使用函数
String result = intToString.apply(10);
System.out.println(result); // 输出: Number is: 10

函数式接口真实落地案例

说明 :WorkManager 在拿到一组任务 ID 后,会先查询数据库中的 WorkSpec.WorkInfoPojo,再用 WORK_INFO_MAPPER 把数据库对象转换成对外暴露的 WorkInfo。这里的 Function<List<WorkInfoPojo>, List<WorkInfo>> 通过 Function 把数据层模型映射为业务层模型,再由 WorkManager 内部把结果投递到线程池上执行后续逻辑(如状态回调、通知等)。

ini 复制代码
public List<WorkInfo> runInternal() {
    WorkDatabase workDatabase = workManager.getWorkDatabase();
    List<WorkSpec.WorkInfoPojo> workInfoPojos =
        workDatabase.workSpecDao().getWorkStatusPojoForIds(ids);
    return WorkSpec.WORK_INFO_MAPPER.apply(workInfoPojos);
}
WORK_INFO_MAPPER函数猜测可能实现
​
​
import androidx.work.Data;
import androidx.work.WorkInfo;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.utils.StringCompat;
​
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
​
// 假设这是 WorkSpec 类内部的静态字段或方法
public static final Function<List<WorkSpec.WorkInfoPojo>, List<WorkInfo>> WORK_INFO_MAPPER =
    new Function<List<WorkSpec.WorkInfoPojo>, List<WorkInfo>>() {
        @Override
        public List<WorkInfo> apply(List<WorkSpec.WorkInfoPojo> input) {
            if (input == null || input.isEmpty()) {
                return new ArrayList<>();
            }
​
            List<WorkInfo> workInfos = new ArrayList<>(input.size());
            for (WorkSpec.WorkInfoPojo pojo : input) {
                // 1. 解析 UUID
                UUID id = UUID.fromString(pojo.id);
​
                // 2. 获取状态
                WorkInfo.State state = pojo.state;
​
                // 3. 解析输入数据
                Data inputData = Data.fromByteArray(pojo.inputData);
​
                // 4. 解析输出数据
                Data outputData = Data.fromByteArray(pojo.outputData);
​
                // 5. 获取标签列表
                List<String> tags = StringCompat.split(pojo.tags, ",");
​
                // 6. 获取运行尝试次数
                int runAttemptCount = pojo.runAttemptCount;
​
                // 7. 构建 WorkInfo 对象
                // 注意:WorkInfo 的构造函数在不同版本可能有差异,以下为典型构造方式
                WorkInfo workInfo = new WorkInfo(
                    id,
                    state,
                    inputData,
                    tags,
                    outputData,
                    runAttemptCount
                );
​
                workInfos.add(workInfo);
            }
            return workInfos;
        }
    };

五、Request 请求的完整执行流程

说明 :从调用方 WorkManager.enqueue(request) 一直到 Worker doWork() 真正运行、再到 WorkInfo 状态对外暴露,整个链路涉及 构建请求 → 持久化 → 平台调度 → 任务包装 → 线程池执行 → 结果回写 → 状态监听 七个阶段。理解这条链路是排查「任务没跑、跑多次、状态不对」等问题的基础。

1. 构建 Request

WorkRequest 是请求的最小单元,常用实现是 OneTimeWorkRequest(一次性)和 PeriodicWorkRequest(周期性)。可以叠加 Constraints(网络、电量、充电等约束)、Data(输入数据)、BackoffPolicy(重试策略)。

scss 复制代码
val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setInputData(workDataOf("image_url" to "https://x/1.jpg"))
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()
    )
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
    .addTag("upload")
    .build()

2. enqueue → 写入数据库

WorkManager.enqueue() 只做一件事:把 WorkRequest 序列化成 WorkSpec,通过 Room 写入本地 SQLite(androidx.work.workdb),并打上 ENQUEUED 状态。

less 复制代码
// WorkManagerImpl#enqueue(简化)
public Operation enqueue(@NonNull WorkRequest request) {
    WorkSpec workSpec = request.getWorkSpec();        // 把 Request 转为持久化模型
    WorkDatabase db = mWorkManager.getWorkDatabase();
    db.workSpecDao().insertWorkSpec(workSpec);        // 落库
    mWorkManager.getProcessor().startWork(...);       // 通知处理器
}

3. 平台级调度器按约束派发

数据库写入后,多个调度器轮询待执行任务,按约束挑选「现在能跑」的交给系统:

scss 复制代码
// SystemJobScheduler#schedule(简化)
public void schedule(WorkSpec... workSpecs) {
    JobInfo jobInfo = new JobInfo.Builder(id, componentName)
        .setRequiredNetworkType(workSpec.constraints.networkType)
        .setRequiresCharging(workSpec.constraints.requiresCharging)
        .setMinimumLatency(workSpec.calculateInitialDelay())
        .setBackoffCriteria(workSpec.backoffPolicy, workSpec.backoffDelay)
        .build();
    jobScheduler.schedule(jobInfo);
}

4. JobService 反向拉起 WorkManager

约束满足后,系统调起 SystemJobService#onStartJob

typescript 复制代码
// SystemJobService
@Override
public boolean onStartJob(JobParameters params) {
    String workSpecId = ...;                                  // 从 params 拿到要执行的任务 ID
    return mWorkManagerImpl.startWork(workSpecId, params);    // 交给 WorkManager 内部处理
}

5. WorkManager 内部:处理器 → WorkerWrapper

Processor 维护一个执行队列,把任务包装成 WorkerWrapper,再分配到合适的 Worker 实例:

scss 复制代码
// Processor#startWork
public boolean startWork(String id, @Nullable JobParameters jobParameters) {
    WorkerWrapper wrapper = mWrappers.get(id);
    if (wrapper == null) {
        wrapper = new WorkerWrapper.Builder(...)
            .withWorkerClassName(workSpec.workerClassName)   // Worker 类全限定名
            .withInputData(workSpec.input)
            .withRuntimeExtras(...)
            .build();
        mWrappers.put(id, wrapper);
    }
    wrapper.setJobParameters(jobParameters);
    return wrapper.run();                                    // 进入线程池
}

6. 线程池执行(Worker / CoroutineWorker)

  • 普通 WorkerExecutor 线程池执行 doWork()(同步阻塞,不能 suspend
  • CoroutineWorkerDispatchers.Default 上跑 suspend fun doWork()
  • RxWorker → 返回 Single<T>
  • ListenableWorker → 返回 ListenableFuture
scss 复制代码
// WorkerWrapper#runWorker(简化)
private void runWorker(@NonNull WorkSpec workSpec) {
    WorkerFactory factory = mConfiguration.getWorkerFactory();
    Worker worker = factory.createWorkerWithDefaultFallback(
        appContext, workSpec.workerClassName, params);
    if (worker instanceof CoroutineWorker) {
        // 在协程上下文里调用
        ((CoroutineWorker) worker).startWork().addListener(...);
    } else {
        // 在 Executor 上调用 doWork
        mDefaultExecutor.execute(() -> {
            Result result = worker.doWork();
            handleResult(result);                            // 处理返回值
        });
    }
}

7. 结果回写 → 数据库 → 状态回调

Worker 返回的 ResultWorkerWrapper 处理:写回 WorkSpec.state、决定是否需要重试(依据 runAttemptCountBackoffPolicy)、触发依赖链上的下一个任务。

typescript 复制代码
// WorkerWrapper#handleResult
private void handleResult(Result result) {
    switch (result) {
        case SUCCESS: markWorkSpecFinished(WorkInfo.State.SUCCEEDED); break;
        case FAILURE: markWorkSpecFinished(WorkInfo.State.FAILED);     break;
        case RETRY:   rescheduleWithBackoff();                         break;
    }
    // 通知 Processor 本轮结束,触发下一次调度
    mProcessor.onExecuted(mWorkSpecId, result);
}

8. 业务侧监听

前端通过 LiveData / Flow 拿到 WorkInfo,做 UI 提示:

scss 复制代码
WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(request.id)
    .observe(this) { info ->
        when (info.state) {
            WorkInfo.State.ENQUEUED -> log("已入队")
            WorkInfo.State.RUNNING  -> showProgress()
            WorkInfo.State.SUCCEEDED -> toast("上传成功")
            WorkInfo.State.FAILED    -> toast("上传失败")
            WorkInfo.State.BLOCKED   -> log("等待约束满足")
            WorkInfo.State.CANCELLED -> log("已取消")
        }
    }
相关推荐
Android打工仔1 小时前
不要再把 Flow 当作一个容器
android·kotlin
tryxr1 小时前
Chat2Excel 项目用户服务开发
java·数据库·java项目·用户服务开发
CodeStats1 小时前
【Java数据类型】Java底层数据类型原理(下):时间API、JSON与String存储演进
java·开发语言·数据类型·string·byte
Co_Hui1 小时前
Java Synchronized 关键字
java
lhldsg1 小时前
折扣卡CPS小程序定制全流程实战指南:从需求分析到上线部署
java·前端·小程序
她的男孩1 小时前
多租户隔离怎么落地?拆完这1600行Starter源码,我把5个坑全踩明白了
java·后端·架构
ly76891 小时前
Spring 事件机制在微服务中实现最终一致性的设计
java·spring·微服务·异步处理·最终一致性·spring事件·事务事件
wuminyu1 小时前
Kafka利用sendfile与Page Cache实现高性能传输剖析
java·linux·c语言·jvm·c++
我命由我123451 小时前
Android 开发问题:android.permission.CAMERA...duplicated with element declared at
android·java·java-ee·android studio·android jetpack·android-studio·android runtime