Android Jetpack - 使用WorkManager处理简单的后台任务

本文深入探讨了Android Jetpack组件WorkManager的使用方法与最佳实践,包括其特性、工作原理及如何处理后台任务。

转载:原文地址http://hukai.me/google-io-2018-android-jetpack-workmanager/

时间来到2018年的当下,当我们讨论后台处理任务的时候,一般可能涉及的行为类型有下面一些类型,例如:

发送程序运行日志
上传图片和视频
同步数据
处理数据
这些行为都需要在后台进行操作,在Android平台上,我们可以利用如下的这些可选方式来实现后台任务:

google_io_2018_android_jetpack_workmanager_01

那么到底我们如何做出合理的选择呢?过去的几年,Android系统随着版本的更新针对电量优化这一块做出了不同程度的限制优化,例如在Android M上的Doze Mode,Android N上的Limit Implicit Broadcast,Android O上的Background Service Limitations以及最新的Android P上面的App Standby Buckets。为了确保后台任务对电量的消耗影响足够小,对待后台任务的处理要更加的慎重小心。

0)Types of Background Work
通常来说,我们可以把所有的后台任务按照任务紧迫性(是马上需要执行的任务/还是可以缓期执行的任务)和任务重要性(是确保一定要被执行的任务/还是最好能够执行的任务)进行四象限的划分。通常来说对于非确保一定要执行的任务,无论时间是否紧迫,我们都可以使用ThreadPool来完成这个任务。对于那些比较重要的又时间紧迫的任务,我们一般会使用Foreground Service来完成这个操作。比较有有意思的是最后一个象限:那些希望确保可以被执行但是又可以接受延期执行的任务。这些任务可以使用JobScheduler/JobDispatcher/AlarmManager/BroadcastReceivers来完成。WorkManager也刚好是用来解决这一类的问题的。

google_io_2018_android_jetpack_workmanager_02

1)WorkManager Features
下面是WorkManager的一些突出特点:

确保可以被执行,并且可以设置执行的限定条件(例如仅仅在有网络连接的时候才进行图片的上传)
同样受到系统后台任务的限制管理(如APP进入Doze Mode的时候,任务不会被执行)
向后兼容;无论是否集成了Google Play Service服务,都是向后兼容的
任务可查询;如论当下在执行什么任务,都是可以直接查询获取到任务状态信息的(例如正在运行的状态是什么,结果是成功还是失败了)
任务可串联;例如执行任务A之前需要任务B或者C先进行完成
任务伺机执行:在条件满足的时候会尽快尝试触发任务的执行,不需要等待JobScheduler的唤醒,也不会需要等待JobScheduler进行批量任务处理的才被执行
WorkManager中的核心类有:

Worker:这个类是真正干活的,工作逻辑都在这里面
WorkRequest:
OneTimeWorkRequest:只执行一次的任务请求
PeriodicWorkRequest:重复执行的任务请求
举个例子:图片上传的后台任务是如何执行的。下面是上传图片的Worker示例:

google_io_2018_android_jetpack_workmanager_03

google_io_2018_android_jetpack_workmanager_04

其中uploadPhoto是执行在后台线程的,返回值可以是成功或者失败,还可以是重试,这意味着告诉系统这个任务需要后面找机会重新执行。有了上面那些基础,接下去就只需要利用Worker创建对应的WorkRequest,并并添加到WorkManager的执行队列中就好了。

google_io_2018_android_jetpack_workmanager_05

正常情况下,放到任务队列中的任务会被立马执行,可是如果遇到网络连接失败的情况,这样就会执行失败。此时我们就可以通过添加限定执行条件来达到优化的目的,例如设置限定只在网络连接成功的时候才进行任务的执行。

google_io_2018_android_jetpack_workmanager_06

2)Observing Work
有了上面的任务触发逻辑之后,那么如何做任务的监听呢?例如正在处理过程中显示一个进度圈,处理成功的时候消失进度等等。我们可以使用如下演示的范例来监听任务的执行状态。

google_io_2018_android_jetpack_workmanager_07

LiveData是Google开发的一个感知生命周期的架构组件。使用这个组件来hook监听request任务的WorkStatus。在WorkStatus里面有任务的id和status,其中status有6种状态,分别是ENQUEUED,RUNNING,SUCCEEDED,FAILED,BLOCKED,CANCELLED。

3)Chaining Work
通常来说,上传任务真正被执行之前,我们会对数据做一次压缩,因为每一个任务都需要在后台进行,并且需要保证执行顺序。我们可以使用下面的示例方式,先进行压缩,成功之后,再进行上传。

google_io_2018_android_jetpack_workmanager_08

只所以可以类似上面那样写,是因为每一步任务返回的都是WorkContinuation,使用它可以对不同的任务进行串联。

google_io_2018_android_jetpack_workmanager_09

如果想要多项任务并发执行,可以同时建立多个WorkRequest,一起交给WorkManager进行执行(根据CPU核心数和架构的不同,并发数量有所差异)。

google_io_2018_android_jetpack_workmanager_10

我们再把任务链设置的更加复杂一点,例如图片要先分别经过不同的滤镜处理,之后再进行压缩,最后才可以上传,那么使用WorkManager该如何实行呢?

google_io_2018_android_jetpack_workmanager_11

4)Inputs and Outputs
任务之间如何进行数据的传递呢?在介绍这个之前,我们需要了解下什么叫做MapReduce。例如,我们想要从三本书里面找出使用最多的词语,先把所有词语都进行计算一遍,然后对词语的使用次数进行排序,最后才可以找出使用最多的词语,我们把这个行为叫做MapReduce。

使用WorkManager的输入和输出数据具备如下的特点:

简单的KEY-VALUE
KEY都是String类型的
VALUE可以是基础数据类型和String
数据本身已经做了序列化处理
限定10KB大小以内
我们使用如下的方式进行输入的数据传递,构造一个map类型的Data,通过WorkManager的setInputData()给Worker进行传输数据。

google_io_2018_android_jetpack_workmanager_12

接下去Worker可以通过getInputData()来获取到输入的数据。

google_io_2018_android_jetpack_workmanager_13

一般来说,我们会需要把处理的结果进行返回,那么使用setOutputData()来完成这个操作就可以了

有意思的事情是,在任务链中,输出的数据一般就是下一个任务的输入。那么当某个环节的一个任务是由多个任务的输出构成的时候,改如何处理呢?

为了解决这个问题,我们需要了解InputMergers,顾名思义,它是用来合并多个输入数据变成一个的。一般来说有两种合并实现的方式(也可以自己自定义)

OverwritingInputMerger(系统默认):按照输入数据的先后顺序,相同KEY会被覆盖,不同的KEY内容会被保留

ArrayCreatingInputMerger:相同KEY的VALUE值进行合并,需要确保VALUE是相同数据类型的,否者会出现异常

5)Cancelling Work
想要取消一个任务,只需要调用cancelWorkById()就好了,但是需要注意的是,这个方法只是尽力而为,因为相关想要取消的任务有可能已经在运行,也有可能已经执行结束了。

6)Tags
前面我们有提到过好几次任务id,这个id是系统自动生成的,类似UUID这样的数值。我们无法通过这个id来判断这是一个什么样的任务,tags就是为了解决这个任务可读性的问题的。我们可以给任务打上一个或者多个tag来标记这是一个什么样的任务,然后可以通过这个tag来查询,取消任务等等。

使用Tag可以给我们提供很大的帮助,我们可以根据不同的模块和依赖给任务设置不同的tag,也可以根据任务的类型进行设置tag,这样就可以方便的进行批量任务操作了。

7)Unique Work
为了解决多个任务的同步问题,引入了Unique Work的机制。它有三种类型,分别为

KEEP:新启动的Unique任务,如果之前已经存在,就继续保留旧的任务,如果不存在,则触发这次新的任务

REPLACE:取消或者删除之前的所有此类Unique任务,使用这次的任务作为最新任务,重复调用多次的时候,会以最后一次为准

APPEND:按照添加顺序,逐个执行任务

8)Periodic Work
重复任务和我们之前认知的其他重复任务一样,具备一些如下的特点:

最短间隔时间15分钟(和JobScheduler一样)
同样受系统doze mode和其他的后台任务限制
不可以有任务链
不可以有触发延迟
9)Under The Hood
当系统接受到一个Work任务的时候,会先记录到自己的任务数据库中,接下去系统是如何判断执行的呢?如果任务符合当下执行的条件,那么会由Executor(可自定义,系统默认有实现)立即执行;如果我们的进程已经被杀死,那么任务什么时候可以被执行呢?如果设备运行在>=API 23,会交给JobScheduler触发IPC请求,唤醒我们的进程进行任务的执行;如果设备运行在< API 23的情况下,系统会判断设备是否有Firebase JobDispatcher,如果有会交给它进行处理;如果那些Google Play Service服务都没有,系统会使用AlarmManager和BroadcastReceivers的方式在合适的时候唤醒应用进行处理。

10)Best Practice
何时使用WorkManager呢?下面给一些最佳实践的例子:
OK:上传图片和视频
OK:解析数据并存储到数据库中
NO:从调色板中获取颜色并设置到图片上
NO:解析数据并呈现到视图上进行显示
NO:处理交易的请求
不要使用WorkManager来存储数据,记得只有10kb的限制
记得给不同的任务设置各自的执行限定条件,避免无谓的资源浪费

<think> 由于用户在上一次对话中询问了Jetpack使用TimerTask的方法,并在本次对话中明确要求查找用WorkManager替代TimerTask的方法,因此需要聚焦于WorkManager的解决方案。 核心要点: 1. WorkManagerJetpack中用于管理后台任务的组件,适合处理延迟、周期性或约束条件下的任务。 2. 相比TimerTask(在应用进程退出后停止),WorkManager能保证任务最终执行(即使应用重启)。 3. 使用步骤包括:定义Worker、配置WorkRequest、提交任务、观察状态。 详细步骤: ### 1. **添加依赖** 在`build.gradle`中添加WorkManager依赖: ```groovy dependencies { def work_version = "2.7.1" implementation "androidx.work:work-runtime:$work_version" } ``` ### 2. **创建Worker类** 继承`Worker`并重写`doWork()`方法,定义后台任务逻辑: ```java public class MyTimerWorker extends Worker { public MyTimerWorker(Context context, WorkerParameters params) { super(context, params); } @Override public Result doWork() { // 模拟TimerTask的定时任务逻辑 // 例如:每10分钟执行一次数据同步 syncData(); // 返回执行结果 return Result.success(); } private void syncData() { // 具体任务代码 } } ``` ### 3. **配置WorkRequest** #### 周期性任务(替代TimerTask的循环执行) ```java PeriodicWorkRequest periodicRequest = new PeriodicWorkRequest.Builder(MyTimerWorker.class, 15, TimeUnit.MINUTES) // 最小间隔15分钟 .build(); ``` **注意**:Android限制周期性任务的最小间隔为15分钟[^1]。若需更短间隔,需使用`OneTimeWorkRequest`链式调用(见后文)。 #### 一次性任务(替代TimerTask的延迟执行) ```java OneTimeWorkRequest oneTimeRequest = new OneTimeWorkRequest.Builder(MyTimerWorker.class) .setInitialDelay(10, TimeUnit.SECONDS) // 延迟10秒执行 .build(); ``` ### 4. **提交任务到WorkManager** 在ViewModel或Activity中提交请求: ```java WorkManager.getInstance(context).enqueue(periodicRequest); ``` ### 5. **处理任务结果(可选)** 通过`LiveData`观察任务状态: ```java WorkManager.getInstance(context) .getWorkInfoByIdLiveData(periodicRequest.getId()) .observe(this, workInfo -> { if (workInfo.getState() == WorkInfo.State.SUCCEEDED) { // 任务成功完成 } }); ``` ### 6. **实现短间隔循环的变通方案** 若需短于15分钟的周期(如1秒),使用链式一次性任务: ```java public class MyRecursiveWorker extends Worker { @Override public Result doWork() { // 执行任务逻辑 runTask(); // 递归提交下一次任务(实现循环) OneTimeWorkRequest nextRequest = new OneTimeWorkRequest.Builder(MyRecursiveWorker.class) .setInitialDelay(1, TimeUnit.SECONDS) // 1秒后再次执行 .build(); WorkManager.getInstance(getApplicationContext()).enqueue(nextRequest); return Result.success(); } } ``` **注意**:此方式需谨慎使用,避免无限循环消耗资源。 ### 对比TimerTask与WorkManager | **特性** | **TimerTask** | **WorkManager** | |------------------------|----------------------------------------|-------------------------------------| | 生命周期管理 | 需手动取消,易泄漏 | 自动关联应用生命周期 | | 进程退出后是否可持续 | ❌ 停止执行 | ✅ 系统重启后继续调度 | | 最小间隔 | 1毫秒 | 15分钟(周期性任务) | | 线程模型 | 单后台线程,需自行处理线程切换 | 默认后台线程池,支持同步/异步 | | 约束条件支持 | ❌ 无 | ✅ 网络/Wi-Fi/充电等[^2] | ### 应用场景建议 - **定时数据同步**:用`PeriodicWorkRequest`(如每隔24小时)。 - **延时任务**:用`OneTimeWorkRequest.setInitialDelay()`。 - **高频循环UI更新**:仍推荐`Handler`或协程(WorkManager不适合毫秒级任务)。 ### §§相关问题§§ 1. WorkManager如何保证任务在设备重启后继续执行? 2. 如何取消WorkManager提交的后台任务? 3. Jetpack Compose中如何集成WorkManager的状态监听? </think> ### 在Jetpack使用WorkManager替代TimerTask的方法 WorkManagerJetpack中**专为后台任务调度**设计的组件,相比`TimerTask`具有更强的**生命周期感知能力**、**系统兼容性**(兼容Doze模式)和**任务持久化能力**(设备重启后自动重启任务)。以下是完整替代方案: --- ### 一、核心步骤 #### 1. **添加依赖** 在`build.gradle`中引入WorkManager库: ```groovy dependencies { def work_version = "2.7.1" implementation "androidx.work:work-runtime:$work_version" // Kotlin或Java通用 } ``` #### 2. **定义Worker类** 继承`Worker`并重写`doWork()`,实现任务逻辑: ```kotlin class TimerReplacementWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // 替代TimerTask的核心逻辑(自动在后台线程执行) val data = fetchDataFromNetwork() saveToDatabase(data) return Result.success() // 返回执行结果 } } ``` **关键点**: - `doWork()` 默认在**后台线程池**执行,无需手动切换线程 - 返回值支持`Result.success()`, `Result.failure()`, `Result.retry()` #### 3. **配置任务调度规则** ##### ✅ 周期性任务(替代Timer循环) ```kotlin val periodicRequest = PeriodicWorkRequestBuilder<TimerReplacementWorker>( 15, TimeUnit.MINUTES // 最小间隔15分钟(系统限制) ).apply { // 添加执行约束条件 setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) // 仅在联网时执行 .setRequiresCharging(true) // 充电时执行 .build() ) }.build() ``` ##### ✅ 延迟一次性任务(替代Timer延迟启动) ```kotlin val oneTimeRequest = OneTimeWorkRequestBuilder<TimerReplacementWorker>() .setInitialDelay(10, TimeUnit.SECONDS) // 延迟10秒执行 .build() ``` #### 4. **提交任务到队列** ```kotlin // 在ViewModel或Application中提交 WorkManager.getInstance(context).enqueue(periodicRequest) ``` #### 5. **监听任务状态**(可选) ```kotlin WorkManager.getInstance(context) .getWorkInfoByIdLiveData(periodicRequest.id) .observe(owner) { info -> when (info?.state) { WorkInfo.State.SUCCEEDED -> { /* 任务成功 */ } WorkInfo.State.FAILED -> { /* 任务失败 */ } WorkInfo.State.ENQUEUED -> { /* 任务进入队列 */ } } } ``` --- ### 二、高级用法 #### 1. **短间隔周期任务的变通方案 若需小于15分钟的间隔(如每5分钟): ```kotlin // 在Worker中递归触发新任务 class ShortIntervalWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { doTask() // 执行实际任务 // 递归提交下一次任务(实现短间隔) val nextRequest = OneTimeWorkRequestBuilder<ShortIntervalWorker>() .setInitialDelay(5, TimeUnit.MINUTES) // 5分钟后执行下一次 .build() WorkManager.getInstance(applicationContext).enqueue(nextRequest) return Result.success() } } ``` #### 2. **任务链与依赖关系** ```kotlin // 创建任务链:A -> B -> C val workA = OneTimeWorkRequestBuilder<WorkerA>().build() val workB = OneTimeWorkRequestBuilder<WorkerB>().build() val workC = OneTimeWorkRequestBuilder<WorkerC>().build() WorkManager.getInstance(context) .beginWith(workA) .then(workB) // A成功后执行B .then(workC) // B成功后执行C .enqueue() ``` #### 3. **传递参数** ```kotlin // 设置输入参数 val data = Data.Builder() .putString("KEY_URL", "https://api.example.com") .putInt("KEY_RETRY_COUNT", 3) .build() val request = OneTimeWorkRequestBuilder<MyWorker>() .setInputData(data) .build() // Worker中读取参数 class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { val url = inputData.getString("KEY_URL") val retryCount = inputData.getInt("KEY_RETRY_COUNT", 0) // ... } } ``` --- ### 三、与TimerTask的对比优势 | **特性** | `TimerTask` | `WorkManager` | |------------------------|-------------|------------------------| | 生命周期感知 | ❌ 需手动取消 | ✅ 自动关联Activity/Fragment | | 设备重启后任务持久化 | ❌ 失效 | ✅ 自动恢复 | | Doze模式兼容性 | ❌ 可能失效 | ✅ 系统自动调度 | | 执行约束条件 | ❌ 不支持 | ✅ 支持网络/充电/存储空间等 | | 后台执行限制兼容 | ❌ Android 8+受限 | ✅ 完全兼容 | | 任务链管理 | ❌ 复杂 | ✅ 内置支持 | --- ### 四、最佳实践 1. **在ViewModel中初始化**: 确保任务与UI生命周期解耦 ```kotlin class MyViewModel(application: Application) : AndroidViewModel(application) { private val workManager = WorkManager.getInstance(application) fun startPeriodicTask() { val request = PeriodicWorkRequestBuilder<MyWorker>(1, TimeUnit.HOURS).build() workManager.enqueue(request) } override fun onCleared() { workManager.cancelAllWork() // 避免内存泄漏 } } ``` 2. **自定义Worker工厂**(用于依赖注入): ```kotlin class MyWorkerFactory(private val apiService: ApiService) : WorkerFactory() { override fun createWorker(...): ListenableWorker { return MyWorker(context, workerParams, apiService) } } // 在Application中配置 WorkManager.initialize(appContext, Configuration.Builder() .setWorkerFactory(MyWorkerFactory(RetrofitClient.api)) .build()) ``` 3. **调试工具**: 使用`adb`命令监控任务状态: ```bash adb shell dumpsys jobscheduler # 查看任务队列 ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值