Android Jetpack WorkManager 基础
WorkManager
- 一、WorkManager概述
- 二、使用场景
- 三、例
- 1. 创建Worker类(执行具体任务的类)
- 2. 在Activity或合适的地方安排任务并设置属性
- 四、罗列常用API
- 五、一些疑问
- 1、doWork 方法是异步的么?
- 2、为什么 WorkManager 可以在 应用不启动的场景下调用 worker 类中的doWork方法?
- 3、Worker的 onWork 方法中可以弹通知吗?
- !! 参考地址
一、WorkManager概述
https://developer.android.google.cn/topic/libraries/architecture/workmanager?hl=zh-cn
WorkManager是一个Android Jetpack库,用于在满足特定约束条件下,在后台执行可延迟的、异步的任务。它能够有效地管理后台工作任务,确保任务在合适的时机执行,同时也考虑到了电池优化、设备资源管理等因素。
三种类型的持久性工作:
- 立即执行:必须立即开始且很快就完成的任务,可以加急。
- 长时间运行:运行时间可能较长(有可能超过 10 分钟)的任务。
- 可延期执行:延期开始并且可以定期运行的预定任务。
二、使用场景
-
数据同步任务
- 例如,一个新闻类应用需要定期(如每隔几个小时)从服务器获取最新的新闻数据。使用WorkManager可以方便地设置一个周期性的后台任务来执行数据同步操作。
- 代码示例:
// 定义一个后台任务(继承自Worker类)用于获取新闻数据 public class FetchNewsWorker extends Worker { public FetchNewsWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { // 在这里编写获取新闻数据的代码,比如通过网络请求 try { // 模拟网络请求获取新闻数据 String newsData = performNetworkRequestForNews(); // 处理获取到的数据,比如存储到本地数据库 saveNewsDataToDatabase(newsData); return Result.success(); } catch (Exception e) { return Result.failure(); } } } // 安排任务执行 val request = OneTimeWorkRequestBuilder<FetchNewsWorker>().build() WorkManager.getInstance(context).enqueue(request)
-
图像或文件处理任务
- 比如一个图片编辑应用,当用户编辑完图片后,需要在后台将编辑后的图片进行压缩并保存。WorkManager可以确保这个任务在后台有序执行,不会影响用户体验。
- 假设我们有一个压缩图片的Worker:
public class CompressImageWorker extends Worker { public CompressImageWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { // 获取图片路径参数 String imagePath = getInputData().getString("image_path"); try { // 执行图片压缩操作 Bitmap compressedBitmap = compressImage(imagePath); // 保存压缩后的图片 saveCompressedImage(compressedBitmap); return Result.success(); } catch (Exception e) { return Result.failure(); } } }
- 然后可以这样安排任务:
Data inputData = new Data.Builder().putString("image_path", imagePath).build(); OneTimeWorkRequest compressRequest = new OneTimeWorkRequest.Builder(CompressImageWorker.class).setInputData(inputData).build(); WorkManager.getInstance(context).enqueue(compressRequest);
-
系统维护和清理任务
- 像清除应用缓存这种任务。可以定期(例如每周一次)使用WorkManager安排一个任务来清理应用缓存。
- 代码示例:
public class ClearCacheWorker extends Worker { public ClearCacheWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { try { // 清除应用缓存的代码 clearAppCache(getApplicationContext()); return Result.success(); } catch (Exception e) { return Result.failure(); } } } val cacheRequest = OneTimeWorkRequestBuilder<ClearCacheWorker>().build() WorkManager.getInstance(context).enqueue(cacheRequest)
-
推送通知相关任务
- 在需要根据用户行为或服务器消息来安排推送通知的准备工作时可以使用。例如,一个社交应用需要在用户离线一段时间后,当有新消息时,在后台准备好推送通知相关的数据(如获取消息内容、格式化通知文本等),WorkManager可以用来调度这个任务。
WorkManager在处理需要在后台可靠执行,并且对执行时机有一定要求(如延迟执行、周期执行)的任务场景中非常有用,同时它还能够很好地适配不同的Android设备和系统版本,帮助开发者高效地管理后台任务。
三、例
示例基于Android的Kotlin语言编写,实现的功能是每隔一段时间在后台执行一次网络请求任务(这里简单模拟),并且设置了一些如任务约束、输入输出数据、任务标签等常用属性,以下是详细代码及属性标注说明:
1. 创建Worker类(执行具体任务的类)
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
// 自定义Worker类,继承自Worker,在这里编写具体要执行的任务逻辑
class NetworkRequestWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 模拟网络请求操作,这里简单返回成功结果,实际中替换为真实网络请求代码
try {
Thread.sleep(3000) // 模拟网络请求耗时
val resultData = "模拟网络请求成功,获取的数据示例"
// 将结果数据通过输出数据传递出去
val outputData = workDataOf("result" to resultData)
outputData(outputData)
return Result.success()
} catch (e: Exception) {
return Result.failure()
}
}
}
2. 在Activity或合适的地方安排任务并设置属性
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.work.*
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. 创建任务请求(这里创建周期性任务请求示例,也可以用OneTimeWorkRequest创建一次性任务请求)
val request = PeriodicWorkRequestBuilder<NetworkRequestWorker>(
15, TimeUnit.MINUTES // 【任务执行周期属性】:设置任务每隔15分钟执行一次,可根据需求调整时间单位和时长
)
// 2. 设置任务约束(Constraints)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 【网络约束属性】:要求设备必须处于联网状态才执行任务
.setRequiresBatteryNotLow(true) // 【电量约束属性】:要求设备电量不能过低才执行任务
.build()
)
// 3. 设置任务输入数据(InputData),可以向Worker传递参数
.setInputData(workDataOf("param_key" to "param_value")) // 【输入数据属性】:向Worker传递一个键值对数据,在Worker中可获取使用
// 4. 添加任务标签(Tags),方便后续对任务进行管理、查询等操作
.addTag("network_request_task") // 【任务标签属性】:给任务添加一个自定义标签,便于识别
.build()
// 5. 将任务请求加入到WorkManager队列中等待执行
WorkManager.getInstance(this).enqueue(request)
// 6. 可以选择监听任务执行状态(可选操作)
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this) { workInfo ->
when (workInfo.state) {
WorkInfo.State.RUNNING -> {
// 任务正在执行时的处理逻辑
}
WorkInfo.State.SUCCEEDED -> {
val outputData = workInfo.outputData.getString("result")
// 任务成功完成后的处理逻辑,这里可以获取Worker传递的输出数据进行后续操作
}
WorkInfo.State.FAILED -> {
// 任务失败时的处理逻辑
}
else -> {
// 其他状态处理逻辑
}
}
}
}
}
在上述代码中:
- 任务执行周期属性:通过
PeriodicWorkRequestBuilder
的构造函数参数设置任务周期性执行的时间间隔,这里设置为每隔15分钟执行一次,时间单位可以选择TimeUnit
中的如MINUTES
(分钟)、HOURS
(小时)等不同单位来灵活调整。 - 网络约束属性:利用
Constraints.Builder
的setRequiredNetworkType
方法,指定任务执行时需要的网络条件,例如设置为NetworkType.CONNECTED
表示必须在设备联网时才执行任务,还有其他可选的网络类型条件如NetworkType.UNMETERED
(在不计费的网络下,比如Wi-Fi)等。 - 电量约束属性:通过
setRequiresBatteryNotLow
方法可以设置任务执行对设备电量的要求,设置为true
就是要求电量不能过低,避免在电量紧张时执行一些非必要的后台任务消耗电量。 - 输入数据属性:使用
setInputData
方法可以向Worker传递一些参数数据,在Worker类的doWork
方法中可以通过getInputData
方法获取这些数据,方便根据不同的传入参数执行不同的任务逻辑。 - 任务标签属性:
addTag
方法添加的标签有助于后续对任务进行管理,比如可以根据标签来取消一组特定的任务、查询任务状态等,方便在复杂的任务管理场景中进行区分和操作。
同时,代码中还展示了如何监听任务的执行状态,根据不同的状态进行相应的处理逻辑,比如获取任务成功执行后的输出数据等操作。
记得在项目的build.gradle
(模块级)文件中添加WorkManager依赖,示例中的依赖添加方式如下(Kotlin项目示例):
//implementation 'androidx.work:work-runtime:2.8.1'
//kotlin
implementation 'androidx.work:work-runtime-ktx:2.8.1'
四、罗列常用API
-
任务请求构建相关API
- OneTimeWorkRequestBuilder
- 用途:用于构建一次性执行的工作任务请求。例如,执行一次文件下载任务或一次数据清理任务。
- 示例代码:
OneTimeWorkRequest downloadRequest = new OneTimeWorkRequestBuilder<DownloadWorker>() .build();
- PeriodicWorkRequestBuilder
- 用途:用于构建周期性执行的工作任务请求。比如,定期同步服务器数据或者定时检查系统状态。
- 示例代码:
PeriodicWorkRequest syncRequest = new PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS) .build();
- setConstraints()
- 用途:在构建任务请求时设置任务执行的约束条件。约束条件包括网络类型、电量状态等。
- 示例代码:
Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .build(); OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<MyWorker>() .setConstraints(constraints) .build();
- setInputData()
- 用途:为任务设置输入数据。这些数据可以在
Worker
类的doWork
方法中获取,用于根据不同的输入执行不同的任务逻辑。 - 示例代码:
Data inputData = new Data.Builder() .putString("key", "value") .build(); OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<MyWorker>() .setInputData(inputData) .build();
- 用途:为任务设置输入数据。这些数据可以在
- addTag()
- 用途:为任务添加标签。标签可以用于后续对任务进行管理,如取消一组具有相同标签的任务。
- 示例代码:
OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<MyWorker>() .addTag("my_task_tag") .build();
- OneTimeWorkRequestBuilder
-
任务调度相关API
- WorkManager.getInstance()
- 用途:获取
WorkManager
的实例,用于管理和调度任务。这是使用WorkManager
的入口点。 - 示例代码:
WorkManager workManager = WorkManager.getInstance(context);
- 用途:获取
- enqueue()
- 用途:将任务请求加入到工作队列中,以便
WorkManager
按照约束条件和调度策略执行任务。 - 示例代码:
workManager.enqueue(request);
- 用途:将任务请求加入到工作队列中,以便
- WorkManager.getInstance()
-
任务状态监听相关API
- getWorkInfoByIdLiveData()
- 用途:获取一个
LiveData
对象,用于监听特定任务的状态变化。LiveData
可以方便地与Android的UI组件(如Activity
或Fragment
)结合,实现响应式的UI更新。 - 示例代码:
workManager.getWorkInfoByIdLiveData(request.id) .observe(this, workInfo -> { if (workInfo!= null && workInfo.getState() == WorkInfo.State.SUCCEEDED) { // 任务成功后的处理逻辑 } });
- 用途:获取一个
- getWorkInfoByIdLiveData()
-
Worker类相关API(在自定义Worker类中使用)
- doWork()
- 用途:这是
Worker
类中的核心方法,用于定义任务的具体执行逻辑。在这里编写实际的任务代码,如网络请求、文件处理等。 - 示例代码:
@NonNull @Override public Result doWork() { try { // 执行任务逻辑,例如网络请求 String data = performNetworkRequest(); // 处理请求结果 processData(data); return Result.success(); } catch (Exception e) { return Result.failure(); } }
- 用途:这是
- getInputData()
- 用途:在
doWork
方法中用于获取任务的输入数据,这些数据是通过setInputData
方法设置的。 - 示例代码:
String inputValue = getInputData().getString("key");
- 用途:在
- outputData()
- 用途:用于将任务执行的结果数据输出。这些输出数据可以在任务状态监听时获取。
- 示例代码:
Data outputData = new Data.Builder() .putString("result_key", "result_value") .build(); outputData(outputData);
- doWork()
-
WorkerParameters
概述 主要参数内容WorkerParameters
是一个传递给Worker
类构造函数的参数对象,它包含了与工作任务相关的各种信息,这些信息可以帮助Worker
类中的doWork
方法更好地执行任务。
inputData
(输入数据)- 用途:用于获取在构建工作请求(如
OneTimeWorkRequest
或PeriodicWorkRequest
)时通过setInputData
方法设置的数据。这些数据以键值对的形式存在,可以在doWork
方法中根据键获取相应的值,用于控制任务的具体执行逻辑。 - 示例代码:
@NonNull @Override public Result doWork() { // 获取输入数据 Data inputData = getInputData(); String value = inputData.getString("key"); // 根据获取的值执行任务逻辑 if ("specific_value".equals(value)) { // 执行特定操作 } return Result.success(); }
- 用途:用于获取在构建工作请求(如
runAttemptCount
(运行尝试次数)- 用途:记录了当前工作任务已经尝试运行的次数。当任务执行失败后,
WorkManager
可能会根据重试策略重新尝试执行任务,这个参数可以让Worker
类知道当前是第几次尝试运行,以便根据尝试次数来调整任务执行逻辑,例如在多次尝试失败后采取不同的处理方式。 - 示例代码:
@NonNull @Override public Result doWork() { int attemptCount = getRunAttemptCount(); if (attemptCount > 3) { // 如果尝试次数大于3次,记录错误并返回失败结果 Log.e("MyWorker", "任务多次尝试失败"); return Result.failure(); } try { // 执行任务逻辑 //... return Result.success(); } catch (Exception e) { return Result.retry(); } }
- 用途:记录了当前工作任务已经尝试运行的次数。当任务执行失败后,
taskId
(任务ID)- 用途:每个工作任务都有一个唯一的标识符(ID),通过
taskId
可以获取这个标识符。这个ID在管理任务(如查询任务状态、取消任务等)时非常有用,例如,可以通过WorkManager.getInstance().getWorkInfoByIdLiveData(taskId)
来监听特定任务的状态。 - 示例代码:
@NonNull @Override public Result doWork() { long taskId = getTaskId(); // 可以将taskId用于日志记录或者其他任务管理操作 Log.d("MyWorker", "正在执行任务,任务ID为: " + taskId); try { // 执行任务逻辑 //... return Result.success(); } catch (Exception e) { return Result.retry(); } }
- 用途:每个工作任务都有一个唯一的标识符(ID),通过
五、一些疑问
1、doWork 方法是异步的么?
-
doWork
方法本身是同步的- 在
Worker
类中,doWork
方法的执行是在一个工作线程中进行的,但它本身是同步执行的。这意味着,当doWork
方法被调用时,它会按照代码的顺序依次执行,直到方法执行完毕或者抛出异常。 - 例如,在
doWork
方法中有如下代码:
@NonNull @Override public Result doWork() { try { // 模拟一个耗时操作,比如网络请求或文件读取 Thread.sleep(3000); // 执行其他操作,如数据处理 processData(); return Result.success(); } catch (Exception e) { return Result.failure(); } }
- 这里,
Thread.sleep(3000)
会阻塞当前线程3秒钟,然后再执行processData
操作。整个过程是顺序执行的,类似于普通的同步方法调用。
- 在
-
WorkManager
在整体任务调度上是异步的- 虽然
doWork
方法内部是同步的,但从WorkManager
的任务调度角度来看,整个任务的执行是异步的。 - 当使用
WorkManager
将一个WorkRequest
(如OneTimeWorkRequest
或PeriodicWorkRequest
)加入队列(通过enqueue
方法)后,WorkManager
会在合适的时机(满足任务约束条件,如网络可用、电量充足等)将任务提交到工作线程执行。 - 应用的主线程不会被阻塞,用户可以继续与应用进行交互。例如,在
Activity
中添加任务到WorkManager
队列后:
WorkManager.getInstance(context).enqueue(request); // 主线程可以继续执行其他操作,如更新UI等 updateUI();
- 这里,
updateUI
方法会在主线程中继续执行,而WorkManager
安排的任务会在后台线程中按照自己的节奏执行doWork
方法中的内容。
- 虽然
2、为什么 WorkManager 可以在 应用不启动的场景下调用 worker 类中的doWork方法?
-
工作原理基础
- WorkManager利用了Android系统的作业调度机制。当应用将一个工作请求(如
OneTimeWorkRequest
或PeriodicWorkRequest
)通过WorkManager
的enqueue
方法加入队列后,WorkManager
会与系统的作业调度服务进行交互。 - 这些系统服务在后台持续运行,并且会根据一系列的条件(如设备空闲、电量充足、网络连接符合要求等)以及预先设定的触发规则(如延迟时间、周期时间等)来决定何时启动
worker
类中的doWork
方法执行任务。
- WorkManager利用了Android系统的作业调度机制。当应用将一个工作请求(如
-
与系统服务的集成
- 在Android中,
WorkManager
的底层实现会根据设备的Android版本不同而有所差异,但都依赖于系统原生的作业调度功能。 - 例如,在Android 6.0(API级别23)及以上,
WorkManager
可以使用JobScheduler
。JobScheduler
是Android系统提供的一个用于管理后台任务的服务,它允许应用定义一些条件和触发时机来执行任务。WorkManager
会将工作请求转换为JobScheduler
能够理解的作业任务,并将其交给JobScheduler
来管理。 - 在旧版本的Android(低于API级别23),
WorkManager
会使用BroadcastReceiver
和AlarmManager
的组合来模拟类似的后台任务调度功能。BroadcastReceiver
用于接收系统广播,AlarmManager
用于设置定时任务,通过这种方式来确保在合适的时间启动doWork
方法。
- 在Android中,
-
持久化存储任务信息
WorkManager
会将工作请求的相关信息(如任务类型、约束条件、输入数据等)持久化存储在数据库中。这样,即使应用进程被杀死或者设备重启,系统仍然能够根据存储的信息来恢复和执行未完成的任务。- 例如,当设备重启后,系统的启动过程中会检查
WorkManager
存储的任务信息,然后根据这些信息重新安排任务的执行,从而保证worker
类中的doWork
方法能够在合适的时机被调用。
3、Worker的 onWork 方法中可以弹通知吗?
- 理论上可以弹出通知
- 在
Worker
的doWork
方法中,从功能实现角度来说是可以弹出通知的。因为doWork
方法是在工作线程中执行的,只要按照正确的方式创建和发送通知,就能够在后台任务执行过程中向用户展示通知。 - 例如,在
doWork
方法中可以使用Android的NotificationManager
来构建和发送通知。以下是一个简单的Kotlin示例:
import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build import androidx.core.app.NotificationCompat import androidx.work.Worker import androidx.work.WorkerParameters class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val channelId = "my_channel_id" val channelName = "My Channel" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT) notificationManager.createNotificationChannel(channel) } val builder = NotificationCompat.Builder(applicationContext, channelId) .setSmallIcon(android.R.drawable.ic_dialog_info) .setContentTitle("工作任务通知") .setContentText("后台任务正在执行相关操作") .setPriority(NotificationCompat.PRIORITY_DEFAULT) notificationManager.notify(1, builder.build()) // 这里返回任务结果,假设任务成功执行 return Result.success() } }
- 在
- 需要注意的事项
- 权限问题:需要确保应用具有发送通知的权限。在AndroidManifest.xml文件中,需要添加如下权限声明:
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
- 其中
POST_NOTIFICATIONS
权限是在Android 13(API级别33)及以上版本中用于控制应用发送通知的权限。如果没有这个权限,在这些高版本的Android设备上无法正常发送通知。 - 通知渠道(Android 8.0及以上):从Android 8.0(API级别26)开始,必须为每个通知创建一个通知渠道(
NotificationChannel
)。如上述示例中,在if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
条件判断部分创建了通知渠道。这是为了让用户能够更好地管理应用发送的通知,如设置通知的优先级、是否静音等。 - 用户体验考虑:在后台任务执行过程中弹出通知应该是有合理目的的。例如,通知用户任务的完成情况、提醒用户注意任务执行过程中的重要事项等。过度或不合理地弹出通知可能会对用户体验造成负面影响。
!! 参考地址
https://developer.android.google.cn/topic/libraries/architecture/workmanager?hl=zh-cn
chatgpt
豆包