前语
经过上一篇 Android WorkManager 初探 的介绍,基本了解了 WorkManager 的特色,经过履行单次使命的示例,现已感受到了其强壮之处。下面,了解一下如何运用 WorkManager 履行周期性使命。
周期性使命
在上一篇中咱们了解到,WorkManager 能够依据设备当前的特性(包括网络、电量状态、存储空间等要素)束缚使命履行的条件,一起还能够依据使命履行的结果进行设置不同的重试战略,下面咱们就经过一个日志上传的使命来了解一下 WorkManager 更多的东西。
- 方针:日志上传,每一小时上传一次。只要在设备处于充电且 Wifi 连接时才能够履行。电量低时不允许履行,失利后需求依据指定的战略重试。
- 界说 Worker
class UploadUserLogWork(appContext: Context, workerParameters: WorkerParameters) :
CoroutineWorker(appContext, workerParameters) {
override suspend fun doWork(): Result {
val userId = inputData.getString(INPUT_TAG)
if (TextUtils.isEmpty(userId)) {
return Result.failure(Data.Builder().putString(OUTPUT_TAG, "userId is null").build())
}
val result = uploadLog(userId)
return if (result != null) {
val output = Data.Builder().putString(OUTPUT_TAG, result).build()
Result.success(output)
} else {
Result.retry()
}
}
override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(
1, createNotification(
applicationContext, id, applicationContext.getString(R.string.app_name)
)
)
}
}
- 这儿结合 Kotlin Coroutine 运用,因而界说 Worker 时需求继承 CoroutineWorker。除了完成
doWork
办法之外,还要完成 getForegroundInfo 办法,回来一个履行职务时的 Notification 即可。当然,假如你对 RxJava 更了解,也有相应的基类能够运用。 - 上传日志需求结合
userId
参数,参数为空时直接按失利处理。 - uploadLog 办法假如履行成功,按履行成功处理,否则回来 retry。
private suspend fun uploadLog(userId: String?): String? {
return withContext(Dispatchers.IO) {
Log.i(TAG, "start upload")
delay(20000)
if (System.currentTimeMillis().toInt() % 1000 == 0) {
Log.i(TAG, "upload fail")
null
} else {
Log.i(TAG, "finish upload")
"${userId}_${System.currentTimeMillis()}"
}
}
}
这儿简略模拟一个日志上传的使命,一起依据时刻戳 mock 上传失利的场景,便利测验。
- 创建 WorkRequest
fun createWorkRequest(userId: String?): PeriodicWorkRequest {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
.build()
return PeriodicWorkRequestBuilder<UploadUserLogWork>(1, TimeUnit.HOURS)
.setInputData(workDataOf(INPUT_TAG to userId))
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.LINEAR, 3, TimeUnit.SECONDS)
.addTag(WORK_TAG)
.build()
}
- 首先界说束缚条件,
NetworkType.UNMETERED
的意思是不按用量计费的网络,咱们能够简略理解为 WiFi,非手机流量。其他的束缚条件顾名思义。Constraints 的构造者形式还供给了其他的束缚,比方设备是否处于闲暇,存储容量是否过低等,咱们依据实践情况做选择就好。当然,这些束缚条件有些是互斥的,或许和重试战略是有冲突的,设置不恰当的话会在履行时抛出异常,毕竟万事不能既要又要还要。 - 经过
PeriodicWorkRequestBuilder
创建周期性的使命,这儿是每一小时履行一次,需求留意的是最小间隔不能小于 15 分钟。即使你设置过小的时刻,内部也会束缚到 15 分钟。 - 将 userId 经过 InputData 传入,这个上一篇现已用过了。
-
setBackoffCriteria
是用来设置重试战略的,当 doWork 办法回来Result.retry
的时分,就会依据这儿设置的战略进行重试。这儿的机制是失利后初次推迟 3 秒重试,并且后续时刻按线性增加,也便是 6,9,12 秒的时分履行。默许的增加战略是2的指数级增加。也便是会在 6,12,24 秒履行,这个依据实践场景做调整即可。这儿需求留意的是,初次推迟重试的时刻不得大于 10 秒,一起这个推迟的最大时刻是 5 小时,不会无限制的增加。 - 最终给 WorkRequest 增加 TAG,便利后续查询这个 WokerRequest 的信息及对其进行办理。
- 增加使命到队列
fun triggerWork(application: Application) {
// userId = "123"
val request = createWorkRequest("123")
WorkManager.getInstance(application).enqueueUniquePeriodicWork(
WORK_TAG, ExistingPeriodicWorkPolicy.KEEP, request
)
}
-
enqueueUniquePeriodicWork
办法接受 3 个参数:- uniqueWorkName – 用于唯一标识作业恳求的 String。
- existingWorkPolicy – 此 enum 可奉告 WorkManager:假如已有运用该名称且没有完结的唯一作业链,应履行什么操作,这个枚举有 4 个值。
- REPLACE:用新作业替换现有作业。此选项将取消现有作业。
- KEEP:保留现有作业,并疏忽新作业。
- APPEND:将新作业附加到现有作业的末尾。此方针将导致您的新作业链接到现有作业,在现有作业完结后运转。现有作业将成为新作业的先决条件。假如现有作业变为 CANCELLED 或 FAILED 状态,新作业也会变为 CANCELLED 或 FAILED。假如您期望不管现有作业的状态如何都运转新作业,请改用 APPEND_OR_REPLACE。
- APPEND_OR_REPLACE 函数类似于 APPEND,不过它并不依赖于先决条件作业状态。即使现有作业变为 CANCELLED 或 FAILED 状态,新作业仍会运转。
- work – 要调度的 WorkRequest。
关于周期性的使命只支撑 REPLACE 和 KEEP 这两种战略。
关于日志上传的场景,咱们只需求界说一个使命之后,期望他后续按周期履行即可。因而这儿的战略设置为 KEEP 即可。
- 发动使命
因为是周期性履行的使命,咱们找到一个适宜的方位调用一次 triggerWork
办法即可一了百了了,看一下日志。
这儿为了便利测验,将 uploadLog
办法中产生过错的条件改为了 System.currentTimeMillis().toInt() % 2 == 0
21:45:36.633 25520-25579 WorkManagerPlayground com.engineer.android.mini I start upload
21:45:56.640 25520-25579 WorkManagerPlayground com.engineer.android.mini I upload fail
21:46:06.690 25520-25579 WorkManagerPlayground com.engineer.android.mini I start upload
21:46:26.694 25520-25579 WorkManagerPlayground com.engineer.android.mini I upload fail
21:46:46.775 25520-25579 WorkManagerPlayground com.engineer.android.mini I start upload
21:47:06.780 25520-25579 WorkManagerPlayground com.engineer.android.mini I finish upload
能够看到失利之后,第一次延时 10 秒后进行了重试,第二此延时是 20 秒,并且成功了。
从这个比如能够看到,WorkManager 的设计十分有用,尤其是其依据束缚条件的使命调度,重试战略等。这些在实践事务开发中常常会遇到,比方网络恳求失利后怎样重试?无限轮询,服务器能抗住吗?假如是因为 Bug 导致的意外失利,短时刻内大规模的重试会导致雪崩,产生恶性循环将整个服务打挂。而 WorkManger 内置了线性增加或许是指数级增加的 API 就显得十分友爱。这儿不管是否运用 WorkManager ,看一下源码学习一下这些束缚条件的完成也是很有收获的,看看 Android 团队的人是怎样写代码的,是否有值得学习的地方。
编排使命
除了组织周期性使命,WorkManger 另一个特色便是编排使命 的功用。这儿以官方示例 WorkManagerSample ,中的代码做示范。
下面的功用是对一张图片做滤镜,首先会整理留传文件,然后会依次履行 WaterColor/GrayScale/BlurEffec 这几个滤镜效果,而是否履行时依据传入的参数在扩展函数中做判断。最终,依据 save 字段履行 SaveImageToGalleryWorker 将图片保存在本地相册或许是上传到服务器。这个过程中,数据 InputData 会在各个使命之间主动传递,直到有过错产生或许履行成功。
init {
continuation = WorkManager.getInstance(context).beginUniqueWork(
Constants.IMAGE_MANIPULATION_WORK_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.from(CleanupWorker::class.java)
).thenMaybe<WaterColorFilterWorker>(waterColor).thenMaybe<GrayScaleFilterWorker>(grayScale)
.thenMaybe<BlurEffectFilterWorker>(blur).then(
if (save) {
workRequest<SaveImageToGalleryWorker>(tag = Constants.TAG_OUTPUT)
} else {
workRequest<UploadWorker>(tag = Constants.TAG_OUTPUT)
}
)
}
/**
* Applies a [ListenableWorker] to a [WorkContinuation] in case [apply] is `true`.
*/
private inline fun <reified T : ListenableWorker> WorkContinuation.thenMaybe(
apply: Boolean
): WorkContinuation {
return if (apply) {
then(workRequest<T>())
} else {
this
}
}
这儿经过界说 thenMaybe
扩展函数完成了依据参数操控使命履行与否的封装。这儿其实特别像 RxJava 或许是 Java StreamAPI, 经过流式 API 将一些复杂的操作串起来履行,尤其是当这些使命有依赖联系的时分,WorkManager 供给的这类编排机制就更加友爱了。日常开发中,Application 中常常会有大量的初始化,而这些初始常常会有依赖联系,比方很多三方库依赖网络库的初始化,有些则依赖系统权限的获取,总归时刻久了就变成一锅粥了。假如能合理的运用 WorkMangaer 对这些使命进行编排,势必会削减后期的维护成本。
关于初始化
这两篇关于 WorkManager 的文章,始终没有提及 WorkManager 的初始化。其实他和大名鼎鼎的 LeakCanary 一样,是经过 ContentProvider 进行初始化,假如你的应用对发动时刻(不管是冷发动仍是热发动)都十分灵敏的话,能够经过以下方法移除 WorkManager 的初始化。
<!-- If you want to disable android.startup completely. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
</provider>
但一起,你需求自己在适宜的方位手动进行初始化。
小结
WorkManger 的功用十分强壮,因为其使命是经过 SQLite 数据库耐久化存储在本地,因而相比以往的线程机制,对使命的可控性强了很多,尤其是周期性的使命。不管是使命进度的查询,仍是使命的取消都能够结合其 API 完成。更多的功用能够参阅官方供给的指导文档,结合实践事务场景进行运用,会有更具体的领会和认识。