一、介绍
通过上一篇文,Android Jetpack组件之WorkManager后台任务管理的介绍与使用(一)_蜗牛、Z的博客-CSDN博客
我们可以弄清楚workmanager从接入到使用的基本流程。基本可以满足我们日常。那只是简单的入门。如果遇到更复杂的功能,那简单的就无法满足。
二、管理进阶
单一执行:
WorkManager.getInstance(requireContext()).enqueue(myWork)
唯一:
唯一工作既可用于一次性工作,也可用于定期工作。您可以通过调用以下方法之一创建唯一工作序列,具体取决于您是调度重复工作还是一次性工作
- WorkManager.enqueueUniqueWork()(用于一次性工作)
这个只支持OneTimeWorkRequest
- WorkManager.enqueueUniquePeriodicWork()(用于定期工作)
只支持PeriodicWorkRequest
这两种方法都接受 3 个参数:
- uniqueWorkName - 用于唯一标识工作请求的
String
。 - existingWorkPolicy - 此
enum
可告知 WorkManager:如果已有使用该名称且尚未完成的唯一工作链,应执行什么操作。如需了解详情,请参阅冲突解决政策。 - work - 要调度的
WorkRequest
。
冲突解决政策
对于一次性工作,您需要提供一个 ExistingWorkPolicy,它支持用于处理冲突的 4 个选项。
- REPLACE:用新工作替换现有工作。此选项将取消现有工作。
- KEEP:保留现有工作,并忽略新工作。
- APPEND:将新工作附加到现有工作的末尾。此政策将导致您的新工作链接到现有工作,在现有工作完成后运行。
现有工作将成为新工作的先决条件。如果现有工作变为 CANCELLED
或 FAILED
状态,新工作也会变为 CANCELLED
或 FAILED
。如果您希望无论现有工作的状态如何都运行新工作,请改用 APPEND_OR_REPLACE
。
- APPEND_OR_REPLACE 函数类似于
APPEND
,不过它并不依赖于先决条件工作状态。即使现有工作变为CANCELLED
或FAILED
状态,新工作仍会运行。
对于定期工作,您需要提供一个 ExistingPeriodicWorkPolicy,它支持 REPLACE
和 KEEP
这两个选项。这些选项的功能与其对应的 ExistingWorkPolicy 功能相同。
三、如何获取worker对象
在将工作加入队列后,您可以随时按其 name
、id
或与其关联的 tag
在 WorkManager 中进行查询,以检查其状态
三种:
//by uuid workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo> // by name workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>> // by tag workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
监听器:
利用每个方法的 LiveData 变种,您可以通过注册监听器来观察 WorkInfo
的变化
val resultData = WorkManager.getInstance(application).getWorkInfoByIdLiveData(id)
resultData.observe(this) {
if (it?.state == WorkInfo.State.SUCCEEDED) {
//
}
}
复杂的work查询:WorkQuery
WorkManager 2.4.0 及更高版本支持使用 WorkQuery 对象对已加入队列的作业进行复杂查询。WorkQuery 支持按工作的标记、状态和唯一工作名称的组合进行查询。
val workQuery = WorkQuery.Builder
.fromTags(listOf("one1"))
.addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
.addUniqueWorkNames(
listOf("preProcess", "sync")
)
.build()
val workInfos = WorkManager.getInstance(application).getWorkInfos(workQuery)
解释:
WorkQuery
中的每个组件(标记、状态或名称)与其他组件都是 AND
逻辑关系。组件中的每个值都是 OR
逻辑关系
例如:(name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)
四、取消和停止work
// by id
workManager.cancelWorkById(build.id)
// by name
workManager.cancelUniqueWork("sync")
// by tag
workManager.cancelAllWorkByTag("syncTag")
五、停止正在运行的WORK
正在运行的 Worker
可能会由于以下几种原因而停止运行:
- 您明确要求取消它(例如,通过调用
WorkManager.cancelWorkById(UUID)
取消)。 - 如果是唯一工作,您明确地将 ExistingWorkPolicy 为 REPLACE 的新
WorkRequest
加入到了队列中。旧的WorkRequest
会立即被视为已取消。 - 您的工作约束条件已不再满足。
- 系统出于某种原因指示您的应用停止工作。如果超过 10 分钟的执行期限,可能会发生这种情况。该工作会调度为在稍后重试。
在您的工作器停止后,WorkManager 会立即调用 ListenableWorker.onStopped(),您可以调用 ListenableWorker.isStopped() 方法以检查工作器是否已停止
观察worker的中间进度
WorkManager 为设置和观察工作器的中间进度添加了一流的支持。如果应用在前台运行时,工作器保持运行状态,那么也可以使用返回 WorkInfo 的 LiveData 的 API 向用户显示此信息
只有在 ListenableWorker 运行时才能观察到和更新进度信息。如果尝试在 ListenableWorker 完成执行后在其中设置进度,则将会被忽略。您还可以使用 getWorkInfoBy…() 或 getWorkInfoBy…LiveData() 方法来观察进度信息。这两个方法会返回 WorkInfo 的实例,后者有一个返回 Data 的新 getProgress() 方法
六、更新进度
对于使用 ListenableWorker 或 Worker 的 Java 开发者,setProgressAsync() API 会返回 ListenableFuture<Void>;更新进度是异步过程,因为更新过程涉及将进度信息存储在数据库中。在 Kotlin 中,您可以使用 CoroutineWorker 对象的 setProgress() 扩展函数来更新进度信息
class MyCoroutineWorker(
appContext: Context,
params: WorkerParameters
) : CoroutineWorker(appContext, params) {
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.success()
}
override suspend fun getForegroundInfo(): ForegroundInfo {
val id = Random.nextInt(0, Int.MAX_VALUE)
return ForegroundInfo(id, getNotion())
}
private fun getNotion(): Notification {
val notification = Notification()
return notification;
}
}
观察信息:
val liveData =
WorkManager.getInstance(application).getWorkInfoByIdLiveData(coroutineWorker.id)
liveData.observe(this, Observer { workinfo: WorkInfo? ->
if (workinfo != null) {
val datta = workinfo.progress
val state = workinfo.state
if (datta != null && datta.size() > 0) {
}
}
})
注意:
workDataOf(Progress to 0):
workDataOf是Data类里面的,workDataOf直接返回data对象,有因为这个data的存储是Map,所以Progress to 0=map(key,value)=map(Progress ,0)
在LiveData的observe(this)中,it.progress是Data对象,直接通过map对象去获取
加入队列:
val coroutineWorker= PeriodicWorkRequestBuilder<MyCoroutineWorker>(20, TimeUnit.MINUTES).build()
WorkManager.getInstance(application).enqueue(coroutineWorker)
七、链接work
可以使用 WorkManager 创建工作链并将其加入队列。工作链用于指定多个依存任务并定义这些任务的运行顺序。当您需要以特定顺序运行多个任务时
如需创建工作链,您可以使用 WorkManager.beginWith(OneTimeWorkRequest) 或 WorkManager.beginWith(List<OneTimeWorkRequest>),这会返回 WorkContinuation 实例。
然后,可以使用 WorkContinuation 通过 then(OneTimeWorkRequest) 或 then(List<OneTimeWorkRequest>) 添加 OneTimeWorkRequest 依赖实例。每次调用 WorkContinuation.then(...)
都会返回一个新的 WorkContinuation
实例。如果添加了 OneTimeWorkRequest
实例的 List
,这些请求可能会并行运行
最后,您可以使用 WorkContinuation.enqueue() 方法对 WorkContinuation 工作链执行 enqueue() 操作
如下:
val work= OneTimeWorkRequest.from(MyWorks::class.java)
WorkManager.getInstance(application).beginWith(work);
WorkManager.getInstance(application).beginWith(work).then(work).then(work);
这种顺序类似属性动画一样,后面按顺序执行。
八、输入合并器
当您链接 OneTimeWorkRequest
实例时,父级工作请求的输出将作为子级的输入传入。因此,在上面的示例中,plantName1
、plantName2
和 plantName3
的输出将作为 cache
请求的输入传入。
WorkManager 提供两种不同类型的 InputMerger
:
-
OverwritingInputMerger 会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。
-
ArrayCreatingInputMerger 会尝试合并输入,并在必要时创建数组
OverwritingInputMerger
OverwritingInputMerger
是默认的合并方法。如果合并过程中存在键冲突,键的最新值将覆盖生成的输出数据中的所有先前版本。如果每种植物的输入都有一个与其各自变量名称("plantName1"
、"plantName2"
和 "plantName3"
)匹配的键,传递给 cache
工作器的数据将具有三个键值对。如果存在冲突,那么最后一个工作器将在争用中“取胜”,其值将传递给 cache。
由于工作请求是并行运行的,因此无法保证其运行顺序。在上面的示例中,plantName1
可以保留值 "tulip"
或 "elm"
,具体取决于最后写入的是哪个值。如果有可能存在键冲突,并且您需要在合并器中保留所有输出数据,那么 ArrayCreatingInputMerger
可能是更好的选择。
ArrayCreatingInputMerger
将每个键与数组配对。如果每个键都是唯一的,您会得到一系列一元数组,如果存在任何键冲突,那么所有对应的值会分组到一个数组中。
久、链接和work状态
要工作成功完成(即,返回 Result.success()
),OneTimeWorkRequest
链便会按顺序执行。运行时,工作请求可能会失败或被取消,这会对依存工作请求产生下游影响。
当第一个 OneTimeWorkRequest
被加入工作请求链队列时,所有后续工作请求会被屏蔽,直到第一个工作请求的工作完成为止。
在加入队列且满足所有工作约束后,第一个工作请求开始运行。如果工作在根 OneTimeWorkRequest
或 List<OneTimeWorkRequest>
中成功完成(即返回 Result.success()
),系统会将下一组依存工作请求加入队列。
如果该重试政策未定义或已用尽,或者您以其他方式已达到 OneTimeWorkRequest
返回 Result.failure()
的某种状态,该工作请求和所有依存工作请求都会被标记为 FAILED。
OneTimeWorkRequest
被取消时遵循相同的逻辑。任何依存工作请求也会被标记为 CANCELLED
,并且无法执行其工作。
请注意:
如果要向已失败或已取消工作请求的链附加更多工作请求,新附加的工作请求也会分别标记为 FAILED
或 CANCELLED
。如果您想扩展现有链的工作,请参阅 ExistingWorkPolicy 中的 APPEND_OR_REPLACE
。
十一、调试 WorkManager
启用日志记录
如需确定工作器未正确运行的原因,查看详细的 WorkManager 日志很有帮助。如需启用日志记录功能,您需要使用自定义初始化。首先,通过创建应用了清单合并规则 remove
的新 WorkManager 提供程序来停用 AndroidManifest.xml
中的默认 WorkManagerInitializer(2.6以后用
InitializationProvider)
引入:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
现在,默认 WorkManager 初始化程序已停用,您可以使用按需初始化。为此,android.app.Application
类必须提供 androidx.work.Configuration.Provider
的实现。
class MyApplication() : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.build()
}
Java:
public Configuration getWorkManagerConfiguration() {
Configuration.Builder builder= new Configuration.Builder();
builder.setMinimumLoggingLevel(android.util.Log.DEBUG);
return builder.build();
}
定义自定义 WorkManager 配置后,WorkManager 会在您调用 WorkManager.getInstance(Context) 时进行初始化,而不是在应用启动时自动初始化
启用 DEBUG 日志记录后,系统会开始显示更多包含日志标记前缀 WM-
的日志
从 WorkManager 2.4.0 及更高版本请求诊断信息
在应用的调试 build 中,您可以使用以下命令从 WorkManager 2.4.0 及更高版本请求诊断信息:
adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS" -p "<your_app_package_name>"
这提供了以下方面的信息:
- 在过去 24 小时内完成的工作请求。
- 目前正在运行的工作请求。
- 预定运行的工作请求。
诊断信息如下所示(输出通过 logcat
显示)