01 .WorkManager基础
教程基于 v2.9.0
WorkManager
[ ] [应用架构:数据层 - 使用 WorkManager 调度任务 - Android 开发者 Android Developers](https://developer.android.com/topic/libraries/architecture/workmanager) [ ] [后台工作概览 Background work Android Developers](https://developer.android.com/guide/background)
Persistent work 持久性任务
Types of persistent work
- Immediate: 立即,必须立即开始执行并很快完成的任务。可能会加急
- Long Running: 长时间运行,可能运行很长时间(可能超过 10 分钟)的任务
- Deferrable: 可延迟:稍后启动并可以定时运行的计划任务
不同类型的持久性 Work 如何相互关联:
 |
同样,下表概述了各种类型的工作。
Type | Periodicity | How to access 如何访问 |
---|---|---|
Immediate | One time | OneTimeWorkRequest 和 Worker 。如需加快工作速度,请调用 OneTimeWorkRequest 上的 setExpedited() 。 |
Long Running | One time or periodic 一次性或周期性 | 任何 WorkRequest 或 Worker 。在 Worker 中调用 setForeground() 来处理通知。 |
Deferrable | One time or periodic 一次性或周期性 | PeriodicWorkRequest 和 Worker 。 |
What is WorkManager?
WorkManager 是 Google 推出的 Jetpack 组件, 用于解决 应用在退出或者设备重启后仍需要需要运行任务
的问题.
WorkManager 提供了一个电池友好的 API,封装了 Android 多年演变的后台行为限制。这对于需要执行后台任务的 Android 应用程序来说至关重要。
WorkManager Features
- 最高向后兼容到 API 14
- 在运行 API 23 及以上级别的设备上使用
JobScheduler
- 在运行 API 14-22 的设备上结合使用
BroadcastReceiver
和AlarmManager
- 在运行 API 23 及以上级别的设备上使用
- 添加网络可用性或充电状态等工作约束
- 调度一次性或周期性异步任务
- 监控和管理计划任务
- 将任务链接起来
- 确保任务执行,即使应用或设备重启也同样执行任务
- 遵循低电耗模式等省电功能:
Doze
WorkManager 旨在用于可延迟运行(即不需要立即运行)并且在应用退出或设备重启时必须能够可靠运行的任务。例如:
- 向后端服务发送日志或分析数据
- 定期将应用数据与服务器同步
WorkManager 不适用于应用进程结束时能够安全终止的运行中后台工作,也不适用于需要立即执行的任务。
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dependencies {
val work_version = "2.9.0"
// (Java only)
implementation("androidx.work:work-runtime:$work_version")
// Kotlin + coroutines
implementation("androidx.work:work-runtime-ktx:$work_version")
// optional - RxJava2 support
implementation("androidx.work:work-rxjava2:$work_version")
// optional - GCMNetworkManager support
implementation("androidx.work:work-gcm:$work_version")
// optional - Test helpers
androidTestImplementation("androidx.work:work-testing:$work_version")
// optional - Multiprocess support
implementation "androidx.work:work-multiprocess:$work_version"
}
最新 release 版本查看:release
When use WorkManager?
WorkManager 处理满足各种约束且需要运行的后台工作,无论应用程序进程是否处于活动状态。
当应用程序位于后台时、当应用程序位于前台时或当应用程序在前台启动但转到后台时,可以启动后台工作。
无论应用程序正在做什么,后台工作都应该继续执行,或者在 Android 终止其进程时重新启动。
关于 WorkManager 的一个常见困惑是:它适用于需要在 “ 后台 “ 线程中运行但不需要在进程死亡后继续存在的任务。WorkManager 不是这种情况。对于此用例还有其他解决方案,例如 kotlin 的协程、ThreadPools 或 RxJava 等库,更多关于 Background Task 见:[guide to background processing.](< [后台工作概览 | Background work | Android Developers](https://developer.android.com/develop/background-work/background-tasks)。>) |
总结一句就是: 需要在后台做一些任务,不管 APP 是否已经死掉都是需要执行的任务,用 WorkManager。
How use WorkManager?
- 配置和初始化
WorkManager
,可选 - 定义一个 Worker,具体的工作在其
doWork()
方法执行 - 定义一个 WorkRequest,请求的约束,执行时间,延迟,tag,输入/输出数据等
- 交给 WorkManager 去执行,具体什么时候执行,由系统根据约束条件和系统状态,决定一个最佳执行时机
Configuration
and Initialization
默认初始化
WorkManager 的默认配置适用于大多数 APP。如果您需要更多地控制 WorkManager 管理和安排工作的方式,您可以通过自行初始化 WorkManager 来自定义 WorkManager 配置。
WorkManager 默认在 InitializationProvider
初始化了
1
2
3
4
5
6
7
8
9
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge" >
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup" />
</provider>
具体的初始化逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class WorkManagerInitializer implements Initializer<WorkManager> {
private static final String TAG = Logger.tagWithPrefix("WrkMgrInitializer");
@Override
public WorkManager create(@NonNull Context context) {
// Initialize WorkManager with the default configuration.
Logger.get().debug(TAG, "Initializing WorkManager with default configuration.");
WorkManager.initialize(context, new Configuration.Builder().build());
return WorkManager.getInstance(context);
}
@Override
public List<Class<? extends androidx.startup.Initializer<?>>> dependencies() {
return Collections.emptyList();
}
}
过滤 TAG:WM-
自定义初始化
按需初始化允许您仅在需要该组件时创建 WorkManager,而不是在每次应用程序启动时创建。这样做可以使 WorkManager 脱离关键启动路径,从而提高应用程序启动性能。要使用按需初始化:
Remove the default initializer
要提供您自己的配置,您必须首先删除默认的初始值设定项。为此,请使用合并规则 tools:node="remove"
更新 AndroidManifest.xml
从 WorkManager 2.6 开始,App Startup
在 WorkManager 内部使用。要提供自定义初始值设定项,您需要删除 androidx.startup
节点。
如果您不在应用程序中使用 App Startup
,则可以将其完全删除。
1
2
3
4
5
6
<!-- If you want to disable android.startup completely. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
</provider>
否则,仅删除 WorkManagerInitializer
节点。
1
2
3
4
5
6
7
8
9
10
11
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- If you are using androidx.startup to initialize other components -->
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
当使用低于 2.6 的 WorkManager 版本时,请删除 workmanager-init
:
1
2
3
4
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
如果用了 App Startup
,检查清单文件是否存在如下配置,如果存在,删除掉:
1
2
3
4
5
6
7
8
9
10
11
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- If you are using androidx.startup to initialize other components -->
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
实现 Configuration.Provider
Application 实现 Provider 自动初始化
让您的 Application
类实现 Configuration.Provider
接口,并提供您自己的 Configuration.Provider.getWorkManagerConfiguration
实现。当您需要使用 WorkManager 时,请务必调用方法 WorkManager.getInstance(Context)
。 WorkManager 调用应用程序的自定义 getWorkManagerConfiguration()
方法来发现其 Configuration
。
注意:如果在初始化 WorkManager 之前调用已弃用的无参数
WorkManager.getInstance()
方法,该方法将引发异常。即使您没有自定义 WorkManager,您也应该始终使用WorkManager.getInstance(Context)
方法。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class App : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(RenameWorkerFactory())
.setMinimumLoggingLevel(Log.VERBOSE)
.build()
}
class RenameWorkerFactory: WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
return when(workerClassName) {
"RxCleanupWorker" -> CleanupWorker(appContext, workerParameters)
else -> null
}
}
}
这种方式不需要手动调用 WorkManager.initialize
,这是由于 getInstance()
时会判定 context 是否实现了 Configuration.Provider
,然后调用 initialize()
,属于懒加载的方式,用到的时候再去初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// v2.7.1 WorkManagerImpl.java
public static WorkManager getInstance(Context context) {
return WorkManagerImpl.getInstance(context);
}
public static WorkManagerImpl getInstance(Context context) {
synchronized (sLock) {
WorkManagerImpl instance = getInstance();
if (instance == null) {
Context appContext = context.getApplicationContext();
if (appContext instanceof Configuration.Provider) {
initialize(appContext,((Configuration.Provider) appContext).getWorkManagerConfiguration());
instance = getInstance(appContext);
} else {
// ...
}
}
return instance;
}
}
手动初始化 WorkManager
- 清单文件中移除默认的
InitializationProvider
,添加自定义的InitializationProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!--移除默认的WorkManagerInitializer-->
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
<!--添加自定义的WorkManagerInitializer-->
<meta-data
android:name="me.hacket.assistant.samples.google.architecture.appstartup.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="merge" />
</provider>
- 自定义的
Configuration.Provider
1
- 自定义的
WorkManagerInitializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class WorkManagerInitializer : Initializer<Any> {
override fun create(context: Context) {
val configuration = Configuration.Builder().build()
// WorkManager.initialize(context, configuration) // WorkManager is already initialized. Did you try to initialize it manually without disabling WorkManagerInitializer? See WorkManager#initialize(Context, Configuration) or the class level Javadoc for more information.
return WorkManager.initialize(
context,
MyWorkManagerConfigurationProvider().workManagerConfiguration
)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// No dependencies on other libraries.
return emptyList()
}
}
自定义 WorkerFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 多进程的WorkerFactory
*/
class MultiProcessWorkerFactory : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
return when (workerClassName) {
"com.xxx.appwidget.promotion.AppWidgetPromotionMultiProcessWorker" -> {
AppWidgetPromotionMultiProcessWorker(appContext, workerParameters)
}
else -> {
null
}
}
}
}
初始化设置:
1
2
3
4
5
6
7
8
val builder = Configuration.Builder()
.setMinimumLoggingLevel(if (AppContext.isDebug) android.util.Log.VERBOSE else android.util.Log.WARN)
.setInitializationExceptionHandler {
L.e(msg = "WorkManager initialization failed", it, tag = TAG)
}
.setWorkerFactory(MultiProcessWorkerFactory())
.setDefaultProcessName(currentProcessName)
WorkManager.initialize(application, builder.build())
使用:DelegatingWorkerFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
private fun initInWidgetProcess(application: Application, currentProcessName: String) {
L.d(msg = L.line(">", "initInWidgetProcess"), tag = TAG, false)
val factory = DelegatingWorkerFactory()
factory.addFactory(MultiProcessWorkerFactory())
val builder = Configuration.Builder()
.setMinimumLoggingLevel(if (AppContext.isDebug) android.util.Log.VERBOSE else android.util.Log.WARN)
.setInitializationExceptionHandler {
L.e(msg = "SiWorkManager initialization failed", it, tag = TAG)
}
.setWorkerFactory(factory)
.setDefaultProcessName(currentProcessName)
SiWorkManager.initialize(application, builder.build())
}
WorkManager v 2.1.0 之前的自定义初始化
[自定义 WorkManager 配置和初始化 | Background work | Android Developers](https://developer.android.com/develop/background-work/background-tasks/persistent/configuration/custom-configuration#pre-2.1.0) |
WorkManager 使用步骤
1、定义 Worker
work 是使用 Worker
类定义的。 doWork()
方法在 WorkManager 提供的后台线程上异步运行。
要创建一些工作供 WorkManager
运行,请扩展 Worker
类并重写 doWork()
方法。例如,要创建上传图像的 Worker ,您可以执行以下操作:
1
2
3
4
5
6
7
8
9
10
11
class UploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
// Do the work here--in this case, upload the images.
uploadImages()
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
从 doWork()
返回的 Result
通知 WorkManager
服务工作是否成功,以及在失败的情况下是否应重试工作:
Result.Success()
:工作成功完成。Result.Failure()
:工作失败。Result.Retry()
:工作失败,应根据其重试策略在其他时间尝试 retry policy
2、定义 WorkRequest
WorkRequest 概述
定义工作后,必须使用 WorkManager
服务对其进行调度才能运行。 WorkManager
在如何安排工作方面提供了很大的灵活性。您可以安排它在一段时间间隔内定期 run periodically 运行,也可以安排它仅运行一次 one time。
无论您选择如何安排 work,您都将始终使用 WorkRequest。Worker
定义了 work 单元,WorkRequest
及其子类定义了它的运行方式和时间。在最简单的情况下,使用 OneTimeWorkRequest
。
对于一次性 WorkRequest,请使用 OneTimeWorkRequest
,对于周期性工作,请使用 PeriodicWorkRequest
。
1
2
3
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<UploadWorker>()
.build()
安排 one-time work (一次性任务)
对于不需要额外配置的简单工作,请使用静态方法 from
:
1
val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
对于更复杂的工作,您可以使用构建器:
1
2
val oneTimeWorker = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
WorkManager.getInstance().enqueue(oneTimeWorker)
安排 expedited work(加急任务)
expedited work 概述
WorkManager2.7.0
引入了 expedited
工作的概念。这使得 WorkManager
能够执行重要的工作,同时让系统更好地控制对资源的访问。加急工作具有以下特点:
- Importance: 重要性:
expedited work
适合对用户重要或用户发起的任务。 - Speed: 速度:
expedited work
工作最适合立即开始并在几分钟内完成的短期任务。 - Quotas: 配额:限制前台执行时间的系统级配额决定了
expedited work
是否可以启动。 - Power Management: 电源管理:电源管理限制(例如
Battery Saver
和Doze
)不太可能影响expedited
工作, Power management restrictions - Latency: 延迟:如果系统当前的工作负载允许,系统会立即执行
expedited work
,这意味着它们对延迟敏感,无法安排稍后执行。也就是说如果系统负载不允许,可能会延迟
Expedited work
潜在的用途是在聊天应用程序中,当用户想要发送消息或图像时;同样地,处理付款或订阅流程的应用程序可能也希望使用 expedited work
。
这是因为这些任务对用户很重要,在后台快速执行,需要立即开始,并且即使用户关闭应用程序也应该继续执行
从 WorkManager2.7 开始,您的应用程序可以调用 setExpedited()
来声明 WorkRequest
应使用加急作业尽快运行。以下代码片段提供了如何使用 setExpedited()
的示例:
1
2
3
4
5
6
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
WorkManager.getInstance(context)
.enqueue(request)
在此示例中,我们初始化
OneTimeWorkRequest
的实例并对其调用setExpedited()
。该请求随后将成为加急工作。如果配额允许,它将立即开始在后台运行。如果已使用配额,则OutOfQuotaPolicy
参数指示请求应按normal
、non-expedited work
运行。
向后兼容性和前台服务
- Android12 之前版本
为了保持 expedited jobs
的向后兼容性,WorkManager
可能会在早于 Android12
的平台版本上运行 foreground service
。 foreground service
可以向用户显示通知。Worker
中的 getForegroundInfoAsync()
和 getForegroundInfo()
方法使 WorkManager
能够在您调用 Android12 之前的 setExpedited()
时显示通知。
如果您希望请求任务作为 expedited work
运行,则任何 ListenableWorker
都必须实现 getForegroundInfo
方法。
注意:在旧平台版本上调用 setExpedited 时,未能实现相应的 getForegroundInfo 方法可能会导致运行时崩溃。
- Android12 及以上版本
当面向 Android12
或更高版本时,您仍然可以通过相应的 setForeground
方法使用前台服务。
注意:
setForeground()
在 Android 12 上可能会引发运行时异常,并且如果启动受到限制 Restrictions on starting a foreground service from the background,则可能会引发异常。
Worker
不知道他们正在做的工作是否 expedited
。但当 WorkRequest
加急时,工作人员可以在某些版本的 Android 上显示通知。
为了启用此功能,WorkManager
提供了 getForegroundInfoAsync()
方法,您必须实现该方法,以便 WorkManager
可以在必要时显示通知以启动 ForegroundService
。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class UploadLogWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
Log.d("打印线程", Thread.currentThread().name)
setForegroundAsync(getForegroundInfo())
return Result.success()
}
@SuppressLint("RestrictedApi")
override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
val future = SettableFuture.create<ForegroundInfo>()
future.set(getForegroundInfo())
return future
}
fun getForegroundInfo(): ForegroundInfo {
val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"1",
"hh",
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
val notification = NotificationCompat.Builder(applicationContext, "1")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle(applicationContext.getString(R.string.app_name))
.setContentText("我是一个上传日志的任务")
.build()
return ForegroundInfo(1337, notification)
}
}
在 Android11 上运行程序,发现打印出了日志,并显示了一个任务通知:
 |
小结: 在早于 Android 12 的 API 版本中,加急工作都是由前台服务执行的,而从 Android 12 开始,它们将由加急作业 (expedited job
) 实现。所以,Android12 上并不会显示通知栏
expedited work use CoroutineWorker
如果您使用 CoroutineWorker
,则必须实现 getForegroundInfo()
。然后将其传递给 doWork()
内的 setForeground()
。这样做将在 12 之前的 Android 版本中创建通知。
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
CoroutineWorker(appContext, workerParams) {
override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(NOTIFICATION_ID, createNotification())
}
override suspend fun doWork(): Result {
TODO()
}
private fun createNotification() : Notification {
TODO()
}
}
注意:您应该将 setForeground()
包装在 try/catch
块中以捕获潜在的 IllegalStateException
。当您的应用程序此时无法在前台运行时,可能会发生这些情况。在 Android 12 及更高版本中,您可以使用更详细的 ForegroundServiceStartNotAllowedException
。
Quota policies 配额政策
当您的应用程序达到其执行配额时,您可以控制 expedited work
的情况。要继续,您可以传递 setExpedited()
OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
当系统无法为任务加急处理时,任务变成常规任务OutOfQuotaPolicy.DROP_WORK_REQUEST
如果没有足够的配额,这会导致请求取消,任务删除
系统必须先为其分配执行时间,然后才能运行 expedited work
。执行时间不是 unlimited
。相反,每个应用程序都会收到一定的执行时间配额。当您的应用程序使用其执行时间并达到其分配的 quota
时,您将无法再执行 expedited work
,直到 quota
刷新。这使得 Android 能够更有效地平衡应用程序之间的资源。应用程序可用的执行时间取决于 standby bucket 和进程重要性。
注意: 当您的应用程序位于前台时,quota
不会限制 expedited work
的执行。仅当您的应用程序位于后台或当您的应用程序移至后台时,执行时间配额才适用。 因此,您应该加快您想要在后台继续进行的工作。当您的应用程序位于前台时,您可以继续使用 setForeground()
。
Deferred expedited work
调用作业后,系统会尝试尽快执行给定的 expedited work
。但是,与其他类型的作业一样,系统可能会推迟新加急作业的开始,例如在以下情况下:
- Load: 系统负载过高,当运行的作业过多或系统内存不足时,可能会出现这种情况。
- Quota: 已超出
expedited work quota
限制。expedited work
使用基于App Standby Buckets
的配额系统,并限制滚动时间窗口内的最大执行时间。
用于 expedited work
的配额比用于其他类型后台作业的配额更具限制性。
安排 periodic work (周期性任务)
您的应用程序有时可能需要定期运行某些工作。例如,您可能需要定期备份数据、下载应用程序中的新内容或将日志上传到服务器。
以下是如何使用 PeriodicWorkRequest 创建定期执行的 WorkRequest
对象:
1
2
3
4
5
val saveRequest =
PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
// Additional configuration
.build()
// 在此示例中,工作间隔为一小时。
间隔周期定义为重复之间的最短时间。执行工作线程的确切时间取决于您在 WorkRequest
对象中使用的 constraints
以及系统执行的优化。
可以定义的最小重复间隔为 15 分钟(与 JobScheduler API 相同 JobScheduler API)。
周期性任务需要更加慎重一点. 开启之后如果不注意, 大部分情况下就会一直运行, 这可能带来很不好的用户体验.
灵活的 interval 时间
设置周期性任务的时候, 需要设置 repeatInterval(重复区间)
和 flexInterval(弹性区间)
参数, 配合注释说明:
 |
1
2
3
4
5
[ 弹性区间外 | 弹性区间内 (flex Interval) ][ 弹性区间外 | 弹性区间内 (flex Interval) ]...
[ 任务不运行 | 任务可运行 ][ 任务不运行 | 任务可运行 ]...
\_________________________________________/\________________________________________/...
第一个区间 (repeat Interval) 第二个区间 (repeat Interval) ...(repeat)
- 要使用弹性周期定义定期工作,请在创建
PeriodicWorkRequest
时传递flexInterval
以及repeatInterval
。弹性周期从repeatInterval - flexInterval
开始,到间隔结束。 - 在
repeatInterval - flexInterval
期间任务是不会运行的;需要注意如果设置了 flexInterval,安排的 WorkManager 可能就不会立即运行一次 repeatInterval
最小值是 15 分钟, 而flexInterval
的最小值是 5 分钟, 如果flexInterval
大于repeatInterval
, 也会被修改到和repeatInterval
一样的值.
以下是可以在每 1 小时周期的最后 15 分钟内运行的定期工作的示例(也就是每小时的最后 45 min→ 1 h 之间 )。
1
2
3
4
val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
1, TimeUnit.HOURS, // repeatInterval (the period cycle)
15, TimeUnit.MINUTES) // flexInterval
.build()
repeatInterval
必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS ,flexInterval
必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS 。
Constraints 对周期性任务的影响
您可以对 periodic work
应用约束。例如,您可以向工作请求添加约束,以便工作仅在用户设备充电时运行。在这种情况下,即使定义的重复间隔过去了, PeriodicWorkRequest
也不会运行,直到满足此条件。如果在运行间隔内不满足条件,这可能会导致特定的工作运行被延迟,甚至被跳过。
简单说就是到了间隔时间,如果 constraints
不满足也不会运行。
周期性任务注意
- 默认的周期性任务 enqueue 后,是能立即执行的
- 如果配置了
flexTimeInterval
的话,就不会立即执行了,需要等repeatInterval-flexTimeInterval
执行
1
2
3
4
PeriodicWorkRequestBuilder<W>(
repeatInterval = 15, TimeUnit.MINUTES, // repeatInterval (the period cycle)
flexTimeInterval = 5, TimeUnit.MINUTES
)
- 如何避免 period 任务立即执行的问题:java - Avoid WorkManager to run immediately? - Stack Overflow
periodic worker 测试
测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class LogWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
var i = 1
override suspend fun doWork(): Result {
// Do the work here--in this case, upload the images.
Logger.d(
"hacket.worker",
"LogWorker(PeriodicWork) doWork start i=$i, hash=${System.identityHashCode(this)}"
)
SystemClock.sleep(10_000L)
// Indicate whether the task finished successfully with the Result
// "LogWorker doWork end i=$i, hash=${System.identityHashCode(this)}".logw()
Logger.w(
"hacket.worker",
"LogWorker(PeriodicWork) doWork end i=$i, hash=${System.identityHashCode(this)}"
)
return Result.success()
}
}
测试 log:
1
2
3
4
5
6
7
8
03:38:26.911 D hacket.worker LogWorker doWork start i=1, hash=164275620
03:38:36.911 W hacket.worker LogWorker doWork end i=1, hash=164275620
03:54:27.205 D WidgetsApp onCreate:
03:54:27.459 D hacket.worker LogWorker doWork start i=1, hash=58753849
03:54:37.463 W hacket.worker LogWorker doWork end i=1, hash=58753849
04:09:37.519 D hacket.worker LogWorker doWork start i=1, hash=68406060
04:09:47.521 W hacket.worker LogWorker doWork end i=1, hash=68406060
可以看到,每隔 15min 执行了 1 次任务,每次任务都是一个新的 Worker 实例
更新 Logger 输出代码,输出当前进程信息,对于已经安排的 task,执行的时候也是用最新的 Worker 来输出,例如
1
05:45:14.413 D WidgetsApp WidgetsApp onCreate, hash=116314074 [2024-07-09 17:45:14 - main - me.hacket.AppWidgets - true]
Work constraints(约束)
约束确保工作被推迟,直到满足最佳条件。 WorkManager
可以使用以下约束。
NetworkType | 限制您的工作运行所需的 type of network。例如,Wi-Fi ( UNMETERED )。 |
---|---|
BatteryNotLow | 设置为 true 时,如果设备处于低电量模式,您的工作将不会运行。 |
RequiresCharging | 设置为 true 时,您的工作将仅在设备充电时运行。 |
DeviceIdle | 当设置为 true 时,这要求用户的设备在工作运行之前处于空闲状态。这对于运行批处理操作非常有用,否则可能会对用户设备上活跃运行的其他应用程序产生负面性能影响。 |
StorageNotLow | 设置为 true 时,如果用户设备上的存储空间太低,您的工作将不会运行。 |
要创建一组约束并将其与某些工作关联,请使用 Contraints.Builder()
创建一个 Constraints
实例并将其分配给您的 WorkRequest.Builder()
。
1
2
3
4
5
6
7
8
9
10
11
12
// 设置网络类型
setRequiredNetworkType(networkType: NetworkType)
// 是否运行时电量不要太低
setRequiresBatteryNotLow(requiresBatteryNotLow: Boolean)
// 是否在充电时才运行
setRequiresCharging(requiresCharging: Boolean)
// 是否不太剩余存储空间过低时运行
setRequiresStorageNotLow(requiresStorageNotLow: Boolean)
// 是否在设备空闲时运行, 这个最低版本是 23
setRequiresDeviceIdle(requiresDeviceIdle: Boolean)
// 监听一个本地的 Uri, 第二个参数是否监听 Uri 的子节点. 在 Uri 的内容改变时运行任务, 最低版本是 24
addContentUriTrigger(uri: Uri, triggerForDescendants: Boolean)
示例 1: 例如,以下代码构建一个工作请求,该请求仅在用户的设备正在充电且连接 Wi-Fi 时运行:
1
2
3
4
5
6
7
8
9
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val myWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
.setConstraints(constraints)
.build()
当指定多个约束时,只有满足所有约束时您的工作才会运行。如果在您的工作运行时未满足约束条件,WorkManager 将停止您的工作线程。当满足所有约束时,将重试该工作。
示例 2:
1
2
3
4
5
6
7
8
9
10
11
12
13
// Create a Constraints object that defines when the task should run
val constraints = Constraints.Builder()
.setRequiresDeviceIdle(true) // 设备空闲
.setRequiresCharging(true) // 充电
.setRequiresBatteryNotLow(true) // 不是低电量
.setRequiresStorageNotLow(true) // 不是低存储空间
.setRequiredNetworkType(NetworkType.NOT_ROAMING) // 不是漫游网络类型
.build()
// ...then create a OneTimeWorkRequest that uses those constraints
val oneTimeWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(constraints)
.build()
Delayed Work(Work 延迟)
如果您的工作没有约束,或者在您的工作排队时满足所有约束,系统可能会选择立即运行该工作。如果您不希望立即运行工作,您可以指定工作在最短初始延迟后开始。
下面是如何将工作设置为在排队后至少运行 10 分钟的示例。
1
2
3
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
虽然该示例说明了如何为 OneTimeWorkRequest
设置初始延迟,但您也可以为 PeriodicWorkRequest
设置初始延迟。在这种情况下,只有定期工作的第一次运行会被延迟。
注意:
worker
执行的确切时间还取决于工作请求中使用的约束以及系统优化。WorkManager
旨在在这些限制下提供最佳的行为。
Retry and backoff policy(重试和避退政策)
如果您要求 WorkManager 重试您的工作,您可以从您的 Worker 返回 Result.retry() 。然后,您的工作将根据 backoff delay and backoff policy 重新安排。
- Backoff delay: 退避延迟指定在第一次尝试后重试工作之前等待的最短时间。该值不能小于 10 秒(或 MIN_BACKOFF_MILLIS)。
- Backoff policy: 退避策略定义退避延迟应如何随着后续重试尝试的时间而增加。 WorkManager 支持 2 种退避策略, LINEAR 和 EXPONENTIAL。
每个工作请求都有退避策略和退避延迟。默认策略是 EXPONENTIAL
,延迟 30 秒,但您可以在工作请求配置中覆盖此设置。
以下是自定义退避延迟和策略的示例。
1
2
3
4
5
6
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build()
在此示例中,最小退避延迟设置为允许的最小值,即 10 秒。由于策略是 LINEAR
,每次新尝试,重试间隔将增加大约 10 秒。例如,如果工作在后续操作后继续返回 Result.retry()
,则以 Result.retry()
结束的第一次运行将在 10 秒后再次尝试,然后是 20、30、40 等。尝试。如果退避策略设置为 EXPONENTIAL
,重试持续时间序列将更接近 20、40、80 等。
注意:退避延迟并不精确,重试之间可能会有几秒的变化,但绝不会小于配置中指定的初始退避延迟。
setBackoffCriteria()
在设置了 setRequiresDeviceIdle(boolean)
时候在 build()
会抛出异 java.lang.IllegalArgumentException: Cannot set backoff criteria on an idle mode job
,这是因为 back-off 对这类 job 没意义。
Tag work
每个 work 请求都有一个唯一的标识符 unique identifier,可用于稍后识别该工作,以便取消该工作 cancel 或观察其进度 observe its progress。
如果您有一组逻辑上相关的工作,您可能还会发现标记这些工作项很有帮助。标记允许您一起处理一组工作请求。
例如:
- WorkManager.cancelAllWorkByTag(String) 取消具有特定标记的所有工作请求
- WorkManager.getWorkInfosByTag(String) 返回可用于确定当前工作状态的
WorkInfo
对象列表 WorkManager.getWorkInfosByTagLiveData(String)
会返回LiveData
和具有该标记的所有任务的状态列表
以下代码显示了如何向您的作品添加 “ 清理 “ 标签:
1
2
3
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.addTag("cleanup")
.build()
最后,可以将多个标签添加到单个工作请求中。这些标签在内部存储为一组字符串。要获取与 WorkRequest
关联的标签集,您可以使用 WorkInfo.getTags()。
从您的 Worker
类中,您可以通过 ListenableWorker.getTags() 检索其标签集。
Assign input data 分配输入数据
您的工作可能需要输入数据才能完成工作。例如,处理上传图像的工作可能需要上传图像的 URI 作为输入。
输入值作为键值对存储在 Data 对象中,并且可以在 WorkRequest
中进行设置。WorkManager 在执行工作时会将输入 Data
传递给您的 Workder
。 Worker
类可以通过调用 Worker.getInputData() 访问输入参数。下面的代码显示了如何创建需要输入数据的 Worker
实例以及如何在工作请求中发送它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 创建输入
val inputData = Data.Builder()
.putInt("KEY_FIRST", firstNumber)
.putInt("KEY_SECOND", secondNumber)
.build()
val worker = OneTimeWorkRequestBuilder<MathWorker>()
.setInputData(inputData)
.build()
WorkManager.getInstance().enqueue(worker)
// Worker 类:
class PlusWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 取出参数
val first = inputData.getInt("KEY_FIRST", 0)
val second = inputData.getInt("KEY_SECOND", 0)
val result = first + second // 1 + 2 = 3
val output = Data.Builder()
.putInt("KEY_RESULT", result)
.build()
return Result.success(output)
}
}
// 监听返回
WorkManager.getInstance().getWorkInfoByIdLiveData(worker.id)
.observe(this, Observer { info ->
if (info != null && info.state.isFinished) {
// 获取返回结果, 应该是3
val result = info.outputData.getInt("KEY_RESULT", 0)
}
})
类似地, Data
类可用于输出返回值。输入参数和返回值部分更详细地介绍了输入和输出数据:input parameters and returned values。
按照设计,Data 对象应该很小,值可以是字符串、基元类型或数组变体。如果需要将更多数据传入和传出工作器,应该将数据放在其他位置,例如 Room 数据库。Data 对象的大小上限为 10 KB。
3、提交 WorkRequest
用 enqueue()
方法,提交 WorkRequest
到 WorkManager
:
1
2
3
WorkManager
.getInstance(myContext)
.enqueue(uploadWorkRequest)
worker 执行的确切时间取决于 WorkRequest
中使用的 constraints
以及系统优化。 WorkManager 会在这些限制下提供最佳的执行时机。
一旦定义了 Worker
和 WorkRequest
,最后一步就是将您的工作排队。将工作排入队列的最简单方法是调用 WorkManager enqueue()
方法,并传递要运行的 WorkRequest
。
1
2
val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)
将工作排队时要小心,以避免重复。例如,应用程序可能会尝试每 24 小时将其日志上传到后端服务。
如果您不小心,您可能会多次将同一任务排队,即使该作业只需要运行一次。为了实现此目标,您可以将工作安排为独特的工作:unique work。
work states 状态
Work states
工作状态 WorkerInfo.State
在工作的整个生命周期内,它会经历多个不同的 State
- 如果有尚未完成的前提性工作,则工作处于
BLOCKED
State。 - 如果工作能够在满足 Constraints 和时机条件后立即运行,则被视为处于
ENQUEUED
State。 - 当工作器在活跃地执行时,其处于
RUNNING
State。 - 如果工作器返回
Result.Success()
,则被视为处于SUCCEEDED
状态。这是一种终止 State;只有 OneTimeWorkRequest 可以进入这种 State。 - 相反,如果工作器返回
Result.Failure()
,则被视为处于FAILED
状态。这也是一个终止 State;只有 OneTimeWorkRequest 可以进入这种 State。所有依赖工作也会被标记为 FAILED,并且不会运行。 - 当您明确取消尚未终止的 WorkRequest 时,它会进入
CANCELLED
State。所有依赖工作也会被标记为 CANCELLED,并且不会运行。
One-time work states 一次性工作状态
对于 one-time
工作请求,您的工作以 ENQUEUED
状态开始。
在 ENQUEUED
状态下,只要满足其 Constraints
和初始延迟时间要求,您的工作就有资格运行。从那里它会移动到 RUNNING
状态,然后根据工作结果,它可能会移动到 SUCCEEDED
、 FAILED
,或者可能返回到 ENQUEUED
如果结果是 retry
。在此过程中的任何时刻,工作都可以取消,此时它将转至 CANCELLED
状态。
图 1 说明了一次性工作的生命周期,以及可能将其转变为另一种状态的事件。
 |
SUCCEEDED
、 FAILED
和 CANCELLED
都代表这项工作的最终状态。如果您的工作处于其中任何状态, WorkInfo.State.isFinished()
将返回 true。
Periodic work states 周期性工作状态
成功和失败状态仅适用于 one-time work
and chained work。对于 periodic work,只有一种终止状态 CANCELLED
。这是因为定期工作永远不会结束。每次运行后,无论结果如何,都会重新安排时间。下图描绘了周期性工作的凝聚态:
 |
Blocked state 阻塞状态
有一个最终状态我们还没有提到,那就是 BLOCKED
。此状态适用于 a series, or chain of work
。工作链及其状态图在链接工作中介绍: Chaining work。
Unique Work
unique work
是一个强大的概念,它保证您一次只有一个具有特定名称的工作实例。与 IDs
不同,唯一名称是人类可读的,由开发人员指定,而不是由 WorkManager 自动生成。与 tags 不同,唯一名称仅与单个工作实例相关联。
unique work
可以应用于一次性工作和周期性工作。您可以通过调用其中一种方法来创建唯一的工作序列,具体取决于您是安排重复工作还是一次性工作。
这两种方法都接受 3 个参数:
- uniqueWorkName 用于唯一标识工作请求的
String
。 - existingWorkPolicy 一个
enum
告诉 WorkManager 如果已经存在具有该唯一名称的未完成工作链该怎么办。有关详细信息,请参阅冲突解决政策 conflict resolution policy。 - work 要安排的
WorkRequest
。
使用 unique work
,我们可以解决前面提到的重复调度问题。
1
2
3
4
5
6
7
8
9
10
11
12
val sendLogsWorkRequest =
PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
.setConstraints(Constraints.Builder()
.setRequiresCharging(true)
.build()
)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"sendLogs",
ExistingPeriodicWorkPolicy.KEEP,
sendLogsWorkRequest
)
现在,如果代码在 sendLogs
作业已在队列中时运行,则现有作业将被保留,并且不会添加新作业。
如果您需要逐步建立一长串任务,unique work
顺序也很有用。例如,照片编辑应用程序可能允许用户撤消一长串操作。每个撤消操作都可能需要一段时间,但必须以正确的顺序执行。
在这种情况下,应用程序可以创建一个 “ 撤消 “ 链,并根据需要将每个撤消操作附加到链中。有关更多详细信息,请参阅链接工作:Chaining work。
Conflict resolution policy 相同任务冲突解决政策
对于 Worker 来说, 可以通过 UUID 和 Tag 来保证其唯一性, 这样在需要的时候就可以避免任务重复执行. 但对于连续的任务链, 如果任务多了, 这样的方式会很繁琐. 于是, WorkerManager 也提供了相应的 API 来保证其唯一性.
当安排 unique work
时,您必须告诉 WorkManager
当发生冲突时要采取什么操作。您可以通过在将工作排队时传递一个枚举来完成此操作。
对于一次性工作,您提供一个 ExistingWorkPolicy
,它支持 4 个选项来处理冲突。
REPLACE
此选项取消现有 work,用新的 work 替换已经存在的 work。如果上一个任务处于等待或者未完成的状态, 取消并删除上一个, 执行新的.KEEP
保持现有工作并忽略新工作。如果上一个任务处于等待或者未完成的状态, 什么都不做 (继续等上一个任务执行).APPEND
等现有工作结束再安排新工作:此策略将使您的新工作链接 chained 到现有工作,并在现有工作完成后运行。现有的工作成为新工作的先决条件。如果现有作品变为CANCELLED
或FAILED
,则新作品也是CANCELLED
或FAILED
。如果您希望无论现有工作的状态如何都运行新工作,请改用APPEND_OR_REPLACE
。APPEND_OR_REPLACE
APPEND_OR_REPLACE
的功能与APPEND
类似,只是它不依赖于先决条件工作状态。如果现有工作是CANCELLED
或FAILED
,则新工作仍会运行。
对于周期工作,您提供一个 ExistingPeriodicWorkPolicy
,它支持 2 个选项: REPLACE
和 KEEP
。这些选项的功能与其 ExistingWorkPolicy
选项相同。
Observing your work 观察你的 work
根据 name、id 和 tag 检查状态
在将工作排队后的任何时候,您都可以通过查询 WorkManager 的 name
、 id
或与其关联的 tag
来检查其状态。
1
2
3
4
5
6
// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
该查询返回 WorkInfo
对象的 ListenableFuture
,其中包括作品的 id
、其 tags
、当前的 State
以及通过 Result.success(outputData)
设置任何 output 数据。
每个方法的 LiveData
变体允许您通过注册侦听器来观察 WorkInfo
的更改。例如,如果您想在某些工作成功完成时向用户显示一条消息,您可以按如下方式设置:
1
2
3
4
5
workManager.getWorkInfoByIdLiveData(syncWorker.id).observe(viewLifecycleOwner) { workInfo ->
if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT).show()
}
}
WorkManager 的 getWorkInfosByTagLiveData(tag)
方法被用来观察和获取与特定标签关联的所有工作的状态。返回的是一个 LiveData 列表,可能获取了一些已经完成的工作任务,这是正常现象,因为 WorkInfo 包含作业的最新状态,包括那些已经完成的。为了只获取活动或未完成的工作,你可以过滤 LiveData<List<WorkInfo>>
列表,剔除状态为 SUCCEEDED、FAILED 或 CANCELLED 的 WorkInfo 对象。
1
2
3
4
workManager.getWorkInfosByTagLiveData(tag).observe(this, Observer { workInfos ->
val activeWorkInfos = workInfos?.filterNot { it.state.isFinished }
// 处理活动作业的更新...
})
Complex work queries 复杂的工作查询
WorkManager 2.4.0 及更高版本支持使用 WorkQuery
对象对排队作业进行复杂查询。 WorkQuery 支持通过其 tag(s)
、state
和 unique work name
的组合来查询工作。
以下示例显示如何找到所有带有标签 syncTag
的工作,该工作处于 FAILED
或 CANCELLED
状态,并且具有唯一的工作名称 preProcess
或 sync
。
1
2
3
4
5
6
7
val workQuery = WorkQuery.Builder
.fromTags(listOf("syncTag"))
.addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
.addUniqueWorkNames(listOf("preProcess", "sync")
)
.build()
val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)
WorkQuery
中的每个组件(tag
、state
或 name
)都与其他组件一起使用 AND
进行编辑。组件中的每个值都是 OR
编辑的。例如: (name1 OR name2 OR …) AND (tag1 OR tag2 OR …) AND (state1 OR state2 OR …)
。
WorkQuery
也适用于 LiveData 等效项:getWorkInfosLiveData()
。
Cancelling and stopping work 取消和停止 work
取消 Work
如果您不再需要运行之前排队的工作,您可以要求取消它。工作可以通过其 name
、 id
或与其关联的 tag
取消。
1
2
3
4
5
6
7
8
// by id
workManager.cancelWorkById(syncWorker.id)
// by name
workManager.cancelUniqueWork("sync")
// by tag
workManager.cancelAllWorkByTag("syncTag")
在底层,WorkManager 检查工作的 State
。如果工作已经完成,则不会发生任何事情。否则,工作的状态将更改为 CANCELLED
并且工作将来将不会运行。任何依赖于此工作的 WorkRequest
作业也将是 CANCELLED
。
当前 RUNNING
工作收到对 ListenableWorker.onStopped()
的调用。重写此方法以处理任何潜在的清理。有关更多信息,请参阅停止正在运行的 Worker:stop a running worker。
注意:
cancelAllWorkByTag(String)
取消给定 tag 的所有工作。
Stop a running Worker
您正在运行的 Worker
可能会被 WorkManager 停止,原因有多种:
- 您明确要求取消它(例如,通过调用
WorkManager.cancelWorkById(UUID)
)。 - 对于 unique work,您显式地将新的
WorkRequest
与ExistingWorkPolicy
ofREPLACE
排入队列。旧的WorkRequest
立即被视为已取消。 - 您的
work's constraints
不再满足。 - 系统指示您的应用程序因某种原因停止您的工作。如果超过 10 分钟的执行期限,就会发生这种情况。该 work 计划稍后重试。
在这些情况下,您的 Worker 将停止。
您应该合作中止正在进行的任何 work,并释放您的 Worker 所持有的任何资源。例如,此时您应该关闭数据库和文件的打开句柄。您可以使用两种机制来了解您的 Worker 何时停止。
onStopped()
回调 一旦您的 Worker 停止,WorkManager 就会调用ListenableWorker.onStopped()
。重写此方法以关闭您可能保留的任何资源isStopped()
属性 您可以调用ListenableWorker.isStopped()
方法来检查您的 worker 是否已经停止。如果您在 Worker 中执行长时间运行或重复性操作,则应经常检查此属性并将其用作尽快停止工作的信号。
注意:WorkManager 会忽略收到 onStop 信号的 Worker 设置的 Result
,因为该 Worker 已被视为已停止。
Chaining work 工作链
WorkManager
允许您创建并排队工作链,该工作链指定多个相关任务并定义它们应按什么顺序运行。当您需要按特定顺序运行多个任务时,此功能特别有用。
要创建工作链,您可以使用 WorkManager.beginWith(OneTimeWorkRequest)
或 WorkManager.beginWith(List<OneTimeWorkRequest>)
,它们各自返回一个 WorkContinuation
实例。
然后, WorkContinuation
可用于使用 then(OneTimeWorkRequest)
或 then(List<OneTimeWorkRequest>)
添加依赖的 OneTimeWorkRequest
实例。
每次调用 WorkContinuation.then(…)
都会返回 WorkContinuation
的新实例。如果您添加 List
个 OneTimeWorkRequest
个实例,这些请求可能会并行运行。
最后,您可以使用 WorkContinuation.enqueue()
方法来 enqueue()
您的 WorkContinuation
链。
让我们看一个例子。在此示例中,配置了 3 个不同的 Worker 作业来运行(可能并行)。然后将这些 Worker 的结果连接起来并传递给缓存 Worker 作业。最后,该作业的输出被传递到上传工作线程,该工作线程将结果上传到远程服务器。
1
2
3
4
5
6
7
8
WorkManager.getInstance(myContext)
// Candidates to run in parallel
.beginWith(listOf(plantName1, plantName2, plantName3))
// Dependent work (only runs after all previous work in chain)
.then(cache)
.then(upload)
// Call enqueue to kick things off
.enqueue()
Input Mergers 输入合并
当您 chain OneTimeWorkRequest
实例时,父级工作请求的输出将作为输入传递给子级。因此,在上面的示例中, plantName1
、 plantName2
和 plantName3
的输出将作为输入传递给 cache
请求。
为了管理来自多个父工作请求的输入,WorkManager 使用 InputMerger
。
WorkManager 提供了两种不同类型的 InputMerger
:
OverwritingInputMerger
尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的密钥。ArrayCreatingInputMerger
尝试合并输入,并在必要时创建数组。
如果您有更具体的用例,那么您可以通过子类化 InputMerger
来编写自己的用例。
OverwritingInputMerger 覆盖输入合并
OverwritingInputMerger
是默认的合并方法。如果合并中存在键冲突,则键的最新值将覆盖结果输出数据中的任何先前版本。
例如,如果每个 plant 输入都有一个与其各自变量名称匹配的键( "plantName1"
、 "plantName2"
和 "plantName3"
),则传递给 worker 将拥有三个键值对。
 |
如果存在冲突,则最后完成的工作人员 “ 获胜 “,并且其值将传递给 cache
。
 |
由于您的工作请求是并行运行的,因此您无法保证其运行顺序。在上面的示例中, plantName1
可以保存 "tulip"
或 "elm"
值,具体取决于最后写入的值。如果您有可能发生键冲突并且需要在合并中保留所有输出数据,那么 ArrayCreatingInputMerger
可能是更好的选择。
ArrayCreatingInputMerger 数组创建输入合并
对于上面的示例,假设我们想要保留所有工厂名称 Workers 的输出,我们应该使用 ArrayCreatingInputMerger
。
1
2
3
4
val cache: OneTimeWorkRequest = OneTimeWorkRequestBuilder<PlantWorker>()
.setInputMerger(ArrayCreatingInputMerger::class)
.setConstraints(constraints)
.build()
ArrayCreatingInputMerger
将每个键与一个数组配对。如果每个键都是唯一的,那么您的结果是一系列单元素数组。
 |
如果存在任何键冲突,则任何相应的值都会分组到一个数组中。
 |
Chaining and Work Statuses Chain 和工作状态
只要 OneTimeWorkRequest
链的工作成功完成(即返回 Result.success()
),它们就会按顺序执行。工作请求在运行时可能会失败或被取消,这会对相关工作请求产生下游影响。
当第一个 OneTimeWorkRequest
在工作请求链中排队时,所有后续工作请求都会被阻止,直到第一个工作请求的工作完成。
 |
一旦排队并且满足所有工作约束,第一个工作请求就开始运行。如果根 OneTimeWorkRequest
或 List<OneTimeWorkRequest>
中的工作成功完成(即,它返回 Result.success()
),则下一组相关工作请求将被排队。
 |
只要每个工作请求成功完成,相同的模式就会传播到工作请求链的其余部分,直到链中的所有工作完成。虽然这是最简单且通常是首选的情况,但处理错误状态也同样重要。
当工作人员处理您的工作请求时发生错误时,您可以根据您定义的退避策略重试该请求。重试属于链的一部分的请求意味着将使用提供给它的输入数据重试该请求。任何并行运行的工作都不会受到影响。
 |
如果该重试策略未定义或已用尽,或者您达到 OneTimeWorkRequest
返回 Result.failure()
的某种状态,则该工作请求和所有相关工作请求将被标记为 FAILED.
 |
当 OneTimeWorkRequest
被取消时,同样的逻辑也适用。任何依赖的工作请求也被标记为 CANCELLED
并且它们的工作将不会被执行。
 |
请注意,如果您要将更多工作请求追加到已失败或已取消工作请求的链中,则新追加的工作请求也将分别标记为 FAILED
或 CANCELLED
。如果您想扩展现有链的工作,请参阅 ExistingWorkPolicy. 中的 APPEND_OR_REPLACE
。
创建工作请求链时,相关工作请求应定义重试策略,以确保工作始终及时完成。失败的工作请求可能会导致不完整的链和/或意外的状态。
示例 1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// A, B, C 就会按顺序执行, 如果全部返回成功或者某一个返回失败, 那该任务链就会结束.
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
// A, B 一起运行, 虽然这2个的开始顺序不定, 但是 C 一定是在这2个运行后才运行.
WorkManager.getInstance()
.beginWith(Arrays.asList(workA, workB))
.then(workC)
.enqueue()
// B 一定会在 A 后面运行, D 也一定会在 C 后面运行, 但是 AB 与 CD 这两条链的运行顺序不定, 但是 E 一定是在 B 和 D 都结束后才运行.
val chain1 = WorkManager.getInstance()
.beginWith(workA)
.then(workB)
val chain2 = WorkManager.getInstance()
.beginWith(workC)
.then(workD)
val chain3 = WorkContinuation
.combine(Arrays.asList(chain1, chain2))
.then(workE)
chain3.enqueue()
示例 2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// A, B, C 就会按顺序执行, 如果全部返回成功或者某一个返回失败, 那该任务链就会结束.
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue();
// A, B 一起运行, 虽然这2个的开始顺序不定, 但是 C 一定是在这2个运行后才运行.
WorkManager.getInstance()
.beginWith(Arrays.asList(workA, workB))
.then(workC)
.enqueue();
// B 一定会在 A 后面运行, D 也一定会在 C 后面运行, 但是 AB 与 CD 这两条链的运行顺序不定, 但是 E 一定是在 B 和 D 都结束后才运行.
WorkContinuation chain1 = WorkManager.getInstance()
.beginWith(workA)
.then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
.beginWith(workC)
.then(workD);
WorkContinuation chain3 = WorkContinuation
.combine(Arrays.asList(chain1, chain2))
.then(workE);
chain3.enqueue();
Long-running workers 长时间运行的 Workers
WorkManager 为长时间运行的工作人员提供内置支持。在这种情况下,WorkManager 可以向操作系统提供一个信号,表明在执行此工作时,如果可能的话,进程应保持活动状态。这些 Worker 的运行时间可以超过 10 分钟。
此新功能的示例用例包括批量上传或下载(无法分块)、本地处理 ML 模型或对应用程序用户很重要的任务。
在底层,WorkManager 代表您管理和运行前台服务来执行 WorkRequest
,同时还显示可配置的通知。
ListenableWorker
现在支持 setForegroundAsync()
API, CoroutineWorker
支持暂停 setForeground()
API。这些 API 允许开发人员指定此 WorkRequest
是重要的(从用户角度来看)还是长期运行的。
从 2.3.0-alpha03
开始,WorkManager 还允许您创建 PendingIntent
,它可用于取消工作线程,而无需使用 createCancelPendingIntent()
API 注册新的 Android 组件。此方法在与 setForegroundAsync()
或 setForeground()
API 一起使用时特别有用,可用于添加通知操作以取消 Worker
。
创建和管理长时间的 Worker
Kotlin
开发人员应该使用 CoroutineWorker
。您可以使用该方法的 suspend 版本 setForeground()
而不是使用 setForegroundAsync()
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL) ?: return Result.failure()
val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME) ?: return Result.failure()
// Mark the Worker as important
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputFile)
return Result.success()
}
private fun download(inputUrl: String, outputFile: String) {
// Downloads a file and updates bytes read
// Calls setForeground() periodically when it needs to update
// the ongoing Notification
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = applicationContext.getString(R.string.notification_channel_id)
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(notificationId, notification)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
// Create a Notification channel
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
}
}
为长时间运行的 worker 添加 foreground-service 类型
如果您的应用面向 Android 14(API 级别 34)或更高版本,则必须为所有长时间运行的工作程序指定前台服务类型。如果您的应用面向 Android 10(API 级别 29)或更高版本,并且包含需要访问位置的长时间运行的工作线程,请指示该工作线程使用 location
前台服务类型。
如果您的应用面向 Android 11(API 级别 30)或更高版本,并且包含需要访问摄像头或麦克风的长时间运行的工作线程,请分别声明 camera
或 microphone
前台服务类型。
要添加这些前台服务类型,请完成以下部分中描述的步骤。
- 在应用程序清单中声明工作线程的前台服务类型。在以下示例中,工作人员需要访问位置和麦克风:
1
2
3
4
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="location|microphone"
tools:node="merge" />
- 在运行时指定前台服务类型: 当您调用
setForeground()
或setForegroundAsync()
时,请确保指定前台服务类型。
1
2
3
4
5
private fun createForegroundInfo(progress: String): ForegroundInfo {
// ...
return ForegroundInfo(NOTIFICATION_ID, notification,
FOREGROUND_SERVICE_TYPE_LOCATION or
FOREGROUND_SERVICE_TYPE_MICROPHONE) }
注意:从 Android 14(API 级别 34)开始,当您调用 setForeground()
或 setForegroundAsync()
时,系统会根据服务类型检查特定先决条件。有关更多信息,请参阅前台服务指南:Foreground Service guide。
Worker 进度
WorkManager 为设置和观察 workers
的中间进度提供一流的支持。如果 workder
在应用程序位于 foreground 运行,则还可以使用返回 WorkInfo
的 LiveData
的 API 向用户显示此信息。
ListenableWorker
现在支持 setProgressAsync()
API,这允许它保留中间进度。这些 API 允许开发人员设置 UI 可以观察到的中间进度。进度由 Data
类型表示,它是属性的 serializable
容器(类似于 input
和 output
,并受到相同的限制)。
进度信息只能在 ListenableWorker
运行时观察和更新。在 ListenableWorker
完成执行后设置进度的尝试将被忽略。您还可以使用 getWorkInfoBy…()
或 getWorkInfoBy…LiveData()
方法之一观察进度信息。这些方法返回 WorkInfo
的实例,该实例有一个返回 Data
的新 getProgress()
方法。
Update Progress 更新进度
对于使用 ListenableWorker
或 Worker
的 Java 开发人员, setProgressAsync()
API 返回 ListenableFuture<Void>
;考虑到更新过程涉及将进度信息存储在数据库中,更新进度是异步的。在 kotlin
中,您可以使用 CoroutineWorker
对象的 setProgress()
扩展函数来更新进度信息。
此示例显示了一个简单的 ProgressWorker
。 Worker
在启动时将其进度设置为 0,并在完成时将进度值更新为 100。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import kotlinx.coroutines.delay
class ProgressWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
companion object {
const val Progress = "Progress"
private const val delayDuration = 1L
}
override suspend fun doWork(): Result {
val firstUpdate = workDataOf(Progress to 0)
val lastUpdate = workDataOf(Progress to 100)
setProgress(firstUpdate)
delay(delayDuration)
setProgress(lastUpdate)
return Result.success()
}
}
Observing Progress 观察进度
观察进度信息也很简单。您可以使用 getWorkInfoBy…()
或 getWorkInfoBy…LiveData()
方法,并获取对 WorkInfo
的引用。
以下是使用 getWorkInfoByIdLiveData
API 的示例。
1
2
3
4
5
6
7
8
9
10
WorkManager.getInstance(applicationContext)
// requestId is the WorkRequest id
.getWorkInfoByIdLiveData(requestId)
.observe(observer, Observer { workInfo: WorkInfo? ->
if (workInfo != null) {
val progress = workInfo.progress
val value = progress.getInt(Progress, 0)
// Do something with progress information
}
})
更新已排队的 work
WorkManager 允许您在将 WorkRequest
排入队列后对其进行更新。这在经常更改 constraints
或需要动态更新其工作人员的大型应用程序中通常是必要的。从 WorkManager 版本 2.8.0 开始, updateWork()
API 是执行此操作的方法。
updateWork()
方法允许您即时更改 WorkRequest
的某些方面,而无需经历手动取消并将新的入队的过程。这极大地简化了开发过程。
避免 cancel work
通常,您应该避免取消现有的 WorkRequest 并将新的 WorkRequest enqueue
。这样做可能会导致应用程序重复某些任务,并且可能需要您编写大量附加代码。
请考虑以下示例,了解取消 WorkRequest 可能会导致困难的情况:
- Back-end request: 如果你在 worker 在发送给 server 时取消该 worker,则新的 worker 需要重新开始并计算
- Scheduling: 如果取消
PeriodicWorkRequest
并且希望新的PeriodicWorkRequest
按照相同的调度执行,则需要计算时间偏移以确保新的执行时间与之前的工作要求。
updateWork()
API 允许您更新工作请求的约束和其他参数,而无需取消和排队新请求。
何时 cancel work
在某些情况下,您应该直接取消 WorkRequest
而不是调用 updateWork()
。当您希望更改已排队工作的基本性质时,您应该这样做。
注意:无法使用
updateWork()
更改WorkRequest
中Worker
的类型。例如,如果您已将OneTimeWorkRequest
排入队列并且希望它定期运行,则必须取消请求并安排新的PeriodicWorkRequest
。
何时 update work
想象一个照片应用程序每天备份用户的照片。它已将 PeriodicWorkRequest
排入队列来执行此操作。 WorkRequest
具有要求设备充电并连接到 WiFi
的限制。
然而,用户每天仅使用快速充电器为设备充电 20 分钟。在这种情况下,应用程序可能需要更新 WorkRequest
以放宽充电限制,以便即使设备未充满电,它仍然可以上传照片。
在这种情况下,您可以使用 updateWork()
方法来更新工作请求的约束。
如何 update work?
updateWork()
方法提供了一种更新现有 WorkRequest
的简单方法,而无需 cancel()
并将新的 enqueue()
队列。
要使用更新已排队的 work,请按照下列步骤操作:
- Get the existing ID for enqueued work: 获取您要更新的
WorkRequest
的 ID。您可以使用任何getWorkInfo
API 检索此 ID,或者通过手动保留初始 WorkRequest 中的 ID,以便稍后在将其排入队列之前使用公共属性WorkRequest.id
进行检索。 - Create new WorkRequest: 创建一个新的
WorkRequest
并使用WorkRequest.Builder.setID()
设置其 ID 以匹配现有的WorkRequest
。 - Set constraints: 使用
WorkRequest.Builder.setConstraints()
传递 WorkManager 新约束。 - Call updateWork: 将新的 WorkRequest 传递给
updateWork()
。
Update work example 更新工作示例:演示了如何使用 updateWork()
方法更改用于上传照片的 WorkRequest
的电池限制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
suspend fun updatePhotoUploadWork() {
// Get instance of WorkManager.
val workManager = WorkManager.getInstance(context)
// Retrieve the work request ID. In this example, the work being updated is unique
// work so we can retrieve the ID using the unique work name.
val photoUploadWorkInfoList = workManager.getWorkInfosForUniqueWork(
PHOTO_UPLOAD_WORK_NAME
).await()
val existingWorkRequestId = photoUploadWorkInfoList.firstOrNull()?.id ?: return
// Update the constraints of the WorkRequest to not require a charging device.
val newConstraints = Constraints.Builder()
// Add other constraints as required here.
.setRequiresCharging(false)
.build()
// Create new WorkRequest from existing Worker, new constraints, and the id of the old WorkRequest.
val updatedWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWorker>()
.setConstraints(newConstraints)
.setId(existingWorkRequestId)
.build()
// Pass the new WorkRequest to updateWork().
workManager.updateWork(updatedWorkRequest)
}
updateWork()
返回 ListenableFuture<UpdateResult>
。给定的 UpdateResult
可以具有几个值之一,这些值概述了 WorkManager 是否能够应用您的更改。它还指示何时能够应用更改。
参考: updateWork()
和 UpdateResult
追踪 update 的 generation
每次更新了 WorkRequest
后,generation 都会自动增加 1,这可以让你精准的追踪当前排队的是哪个版本的 WorkRequest。Generations
提供了给你更多的控制当你 osbserving
,tracing
和 testing
WorkRequest 时。
要生成 WorkRequest
,请按照下列步骤操作:
- WorkInfo: 调用
WorkManager.getWorkInfoById()
来检索与您的WorkRequest
对应的WorkInfo
实例。还有更多的getWorkInfoXXX
,见 WorkManager reference - getGeneration: 在
WorkInfo
实例上调用getGeneration()
。返回的Int
对应于WorkRequest
的生成。
请注意,没有 generation
字段或属性,只有 WorkInfo.getGeneration()
方法。
示例:
1
2
3
4
5
6
7
8
// Get instance of WorkManager.
val workManager = WorkManager.getInstance(context)
// Retrieve WorkInfo instance.
val workInfo = workManager.getWorkInfoById(oldWorkRequestId)
// Call getGeneration to retrieve the generation.
val generation = workInfo.getGeneration()
注意:
updateWork()
返回的UpdateResult
不包括WorkRequest
的生成。
更新 work policy
以前,更新 periodic work
的推荐解决方案是将 PeriodicWorkRequest
与策略 ExistingPeriodicWorkPolicy.REPLACE
排队。如果有一个待处理的 PeriodicWorkRequest
具有相同的唯一 id
,则新的 work request
将取消并删除它。此 policy
现已弃用,取而代之的是使用 ExistingPeriodicWorkPolicy.UPDATE
的工作流程。
例如,将 enqueueUniquePeriodicWork
与 PeriodicWorkRequest
一起使用时,您可以使用 ExistingPeriodicWorkPolicy.UPDATE
策略初始化新的 PeriodicWorkRequest
。如果存在具有相同唯一名称的待处理 PeriodicWorkRequest
,WorkManager 会将其更新为新规范。按照此工作流程,没有必要使用 updateWork()
。
注意: OneTimeWorkRequest
不存在类似的更新策略。这是因为您可以将 enqueueUniqueWork
方法与 APPEND
或 APPEND_OR_REPLACE
策略一起使用。这样做会创建一个同名的工人链。因此,WorkManager 无法有效地支持它们的 UPDATE
策略,因为无法决定应更新链中的哪些工作线程。
WorkManager 线程和并发
WorkManager 提供了四种不同类型的工作原语:
- Worker
Worker
是最简单的实现。 WorkManager 自动在后台线程上运行它(也可以覆盖该线程),更多见 [[#Worker]]。
- CoroutineWorker
CoroutineWorker
是 kotlin
用户的推荐实现。 CoroutineWorker
实例公开了用于后台工作的挂起函数。默认情况下,它们运行在 default Dispatcher
,您可以对其进行自定义,更多见 [[#CoroutineWorker]]。
- RxWorker
RxWorker
是 RxJava 用户的推荐实现。如果您现有的大量异步代码都是用 RxJava 建模的,则应该使用 RxWorkers。与所有 RxJava 概念一样,您可以自由选择您所选择的线程策略,更多见 [[#RxWorker]]。
- ListenableWorker
ListenableWorker
是 Worker
、 CoroutineWorker
和 RxWorker
的基类。它适用于 callback-based asynchronous APIs
(例如 FusedLocationProviderClient
)交互并且不使用 RxJava 的 Java 开发人员,更多见 [[#ListenableWorker]]。
Worker
当您使用 Worker
时,WorkManager 会自动在后台线程上调用 Worker.doWork()
。后台线程来自 WorkManager 的 Configuration
中指定的 Executor
。默认情况下,WorkManager 会为您设置一个 Executor
,但您也可以自定义自己的。例如,您可以在应用程序中共享现有的后台执行器,创建单线程 Executor
以确保所有后台工作按顺序执行,甚至指定自定义 Executor
。要自定义 Executor
,请确保手动初始化 WorkManager。
手动配置 WorkManager 时,您可以指定您的 Executor
:
1
2
3
4
5
6
WorkManager.initialize(
context,
Configuration.Builder()
// Uses a fixed thread pool of size 8 threads.
.setExecutor(Executors.newFixedThreadPool(8))
.build())
下面是一个简单的 Worker
示例,它下载网页内容 100 次:
1
2
3
4
5
6
7
8
9
10
11
12
class DownloadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): ListenableWorker.Result {
repeat(100) {
try {
downloadSynchronously("https://www.google.com")
} catch (e: IOException) {
return ListenableWorker.Result.failure()
}
}
return ListenableWorker.Result.success()
}
}
请注意, Worker.doWork()
是同步调用 - 您应该以阻塞方式完成整个后台工作,并在方法退出时完成它。如果您在 doWork()
中调用异步 API 并返回 Result
,您的回调可能无法正常运行。如果您发现自己处于这种情况,请考虑使用 ListenableWorker
(请参阅 ListenableWorker 中的线程)。
当当前运行的 Worker
由于任何原因停止(stopped for any reason)时,它会收到对 Worker.onStopped()
的调用。重写此方法或调用 Worker.isStopped()
来检查代码并在必要时释放资源。当上例中的 Worker
停止时,它可能处于下载项目循环的中间,并且即使已停止,它也会继续这样做。要优化此行为,您可以执行以下操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class DownloadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): ListenableWorker.Result {
repeat(100) {
if (isStopped) {
break
}
try {
downloadSynchronously("https://www.google.com")
} catch (e: IOException) {
return ListenableWorker.Result.failure()
}
}
return ListenableWorker.Result.success()
}
}
一旦 Worker
停止,您从 Worker.doWork()
返回什么就不再重要了; Result
将被忽略。
CoroutineWorker
对于 kotlin
用户,WorkManager 为协程提供了一流的支持。首先,请在您的 Gradle 文件中包含 work-runtime-ktx
in your gradle file 。您应该扩展 CoroutineWorker
,而不是扩展 Worker
,它有 doWork()
的 suspend 版本。例如,如果您想构建一个简单的 CoroutineWorker
来执行一些网络操作:
1
2
3
4
5
6
7
8
9
10
11
class CoroutineDownloadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val data = downloadSynchronously("https://www.google.com")
saveData(data)
return Result.success()
}
}
请注意,CoroutineWorker.doWork()
是一个 suspend 函数。与 Worker
不同,此代码不会在 Configuration
中指定的 Executor
上运行。相反,它默认为 Dispatchers.Default
。您可以通过提供自己的 CoroutineContext
来自定义它。在上面的示例中,您可能希望在 Dispatchers.IO
上执行此工作,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
class CoroutineDownloadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
val data = downloadSynchronously("https://www.google.com")
saveData(data)
return Result.success()
}
}
}
CoroutineWorker
通过取消协程并传播取消信号来自动处理停止。您不需要做任何特殊的事情来处理停工:work stoppages。
查看 CoroutineWorker 源码,默认是在 Dispatchers.Default
调度器上运行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CoroutineWorker {
public open val coroutineContext: CoroutineDispatcher = Dispatchers.Default
public final override fun startWork(): ListenableFuture<Result> {
val coroutineScope = CoroutineScope(coroutineContext + job)
coroutineScope.launch {
try {
val result = doWork()
future.set(result)
} catch (t: Throwable) {
future.setException(t)
}
}
return future
}
}
在不同的进程中运行 CoroutineWorker
您还可以使用 RemoteCoroutineWorker
( ListenableWorker
的实现)将 Worker 绑定到特定进程。
RemoteCoroutineWorker
使用您在构建工作请求时作为输入数据的一部分提供的两个额外参数绑定到特定进程: ARGUMENT_CLASS_NAME
和 ARGUMENT_PACKAGE_NAME
。
以下示例演示了构建绑定到特定进程的工作请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
val PACKAGE_NAME = "com.example.background.multiprocess"
val serviceName = RemoteWorkerService::class.java.name
val componentName = ComponentName(PACKAGE_NAME, serviceName)
val data: Data = Data.Builder()
.putString(ARGUMENT_PACKAGE_NAME, componentName.packageName)
.putString(ARGUMENT_CLASS_NAME, componentName.className)
.build()
return OneTimeWorkRequest.Builder(ExampleRemoteCoroutineWorker::class.java)
.setInputData(data)
.build()
对于每个 RemoteWorkerService
,您还需要在 AndroidManifest.xml
文件中添加服务定义:
1
2
3
4
5
6
7
8
9
10
11
12
<manifest ... >
<service
android:name="androidx.work.multiprocess.RemoteWorkerService"
android:exported="false"
android:process=":worker1" />
<service
android:name=".RemoteWorkerService2"
android:exported="false"
android:process=":worker2" />
...
</manifest>
RxWorker
我们提供 WorkManager 和 RxJava 之间的互操作性。首先,请在 Gradle 文件中除了 work-runtime
之外还包含 work-rxjava3 dependency 依赖项。还有一个 work-rxjava2
依赖项支持 RxJava2。
然后,您应该扩展 RxWorker
,而不是扩展 Worker
。最后重写 RxWorker.createWork()
方法以返回指示执行的 Result
的 Single<Result>
1
2
3
4
5
6
7
8
9
10
11
class RxDownloadWorker(
context: Context,
params: WorkerParameters
) : RxWorker(context, params) {
override fun createWork(): Single<Result> {
return Observable.range(0, 100)
.flatMap { download("https://www.example.com") }
.toList()
.map { Result.success() }
}
}
请注意, RxWorker.createWork()
是在主线程上调用的,但返回值默认是在后台线程上订阅的。您可以重写 RxWorker.getBackgroundScheduler()
来更改订阅线程。
当 RxWorker
为 onStopped()
时,订阅将被丢弃,因此您不需要以任何特殊方式处理停工。
ListenableWorker
在某些情况下,您可能需要提供自定义线程策略。例如,您可能需要处理基于回调的异步操作。 WorkManager 通过 ListenableWorker
支持此用例。 ListenableWorker
是最基本的 worker API; Worker
、 CoroutineWorker
和 RxWorker
均派生自此类。 ListenableWorker
仅在工作应该开始和停止时发出信号,并将线程完全取决于您。开始工作信号是在主线程上调用的,因此手动转到您选择的后台线程非常重要。
抽象方法 ListenableWorker.startWork()
返回 Result
的 ListenableFuture
。 ListenableFuture
是一个轻量级接口:它是一个 Future
,提供附加侦听器和传播异常的功能。在 startWork
方法中,您应该返回一个 ListenableFuture
,一旦操作完成,您将使用该操作的 Result
来设置它。您可以通过以下两种方式之一创建 ListenableFuture
实例:
- 如果使用
Guava
,请使用ListeningExecutorService
。 - 否则,请在 Gradle 文件中包含
councurrent-futures
并使用CallbackToFutureAdapter
。
如果你想基于异步回调执行一些工作,你可以这样做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class CallbackWorker(
context: Context,
params: WorkerParameters
) : ListenableWorker(context, params) {
override fun startWork(): ListenableFuture<Result> {
return CallbackToFutureAdapter.getFuture { completer ->
val callback = object : Callback {
var successes = 0
override fun onFailure(call: Call, e: IOException) {
completer.setException(e)
}
override fun onResponse(call: Call, response: Response) {
successes++
if (successes == 100) {
completer.set(Result.success())
}
}
}
repeat(100) {
downloadAsynchronously("https://example.com", callback)
}
callback
}
}
}
如果你的工作停止了会发生什么?当工作预计停止时, ListenableWorker
的 ListenableFuture
总是被取消。使用 CallbackToFutureAdapter
,您只需添加取消侦听器,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CallbackWorker(
context: Context,
params: WorkerParameters
) : ListenableWorker(context, params) {
override fun startWork(): ListenableFuture<Result> {
return CallbackToFutureAdapter.getFuture { completer ->
val callback = object : Callback {
var successes = 0
override fun onFailure(call: Call, e: IOException) {
completer.setException(e)
}
override fun onResponse(call: Call, response: Response) {
++successes
if (successes == 100) {
completer.set(Result.success())
}
}
}
completer.addCancellationListener(cancelDownloadsRunnable, executor)
repeat(100) {
downloadAsynchronously("https://example.com", callback)
}
callback
}
}
}
在不同的进程中运行 ListenableWorker
您还可以使用 RemoteListenableWorker
( ListenableWorker
的实现)将工作线程绑定到特定进程。
RemoteListenableWorker
使用您在构建工作请求时作为输入数据的一部分提供的两个额外参数绑定到特定进程: ARGUMENT_CLASS_NAME
和 ARGUMENT_PACKAGE_NAME
。
以下示例演示了构建绑定到特定流程的工作请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
val PACKAGE_NAME = "com.example.background.multiprocess"
val serviceName = RemoteWorkerService::class.java.name
val componentName = ComponentName(PACKAGE_NAME, serviceName)
val data: Data = Data.Builder()
.putString(ARGUMENT_PACKAGE_NAME, componentName.packageName)
.putString(ARGUMENT_CLASS_NAME, componentName.className)
.build()
return OneTimeWorkRequest.Builder(ExampleRemoteListenableWorker::class.java)
.setInputData(data)
.build()
迁移 升级到 WorkManager
从 FirebaseJobDispatcher/GCMNetworkManager 升级到 WorkManager
[从 Firebase JobDispatcher 迁移到 WorkManager Background work Android Developers](https://developer.android.com/develop/background-work/background-tasks/persistent/migrate-from-legacy/firebase) [从 GCMNetworkManager 迁移到 WorkManager Background work Android Developers](https://developer.android.com/develop/background-work/background-tasks/persistent/migrate-from-legacy/gcm)
其他
- 未设置 Constraint 的 Worker,锁屏的情况下,也是会调度的
多进程支持
- [[03 .WorkManager多进程支持]]
Ref
[x] [社区说 浅谈 WorkManager 的设计与实现:系统概述 · Issue 66 · qingmei2/blogs · GitHub](https://github.com/qingmei2/blogs/issues/66) [x] [应用架构:数据层 - 使用 WorkManager 调度任务 - Android 开发者 Android Developers](https://developer.android.com/topic/libraries/architecture/workmanager) - WorkManager - MAD Skills - YouTube
[x] [Android WorkManager with an Example 👷♂️ by Furkan Paşaoğlu DigiGeek Medium](https://medium.com/digigeek/android-work-manager-with-an-example-%EF%B8%8F-63d1b29955d4) [ ] [Introducing WorkManager: How It Works, When to Use It, and How to Use It by Abdellah Ibn El Azraq Medium](https://bbluecoder.medium.com/introducing-workmanager-how-it-works-when-to-use-it-and-how-to-use-it-2ea98f75ebbc) - Demos:AndroidAppWidget