1、什么是WorkManager
按照官方描述,WorkManager 是适合用于持久性作业的推荐解决方案。假如作业始终要经过运用重启和体系从头启动来调度,便是持久性的作业。因为大多数后台处理操作都是经过持久性作业完成的,因而 WorkManager 是适用于后台处理操作的首要推荐 API。
2、使命类型
WorkManager使命类型分为当即运转、长时间运转和延期履行,运用办法与周期关系如下所示:
当即 | 一次性 | OneTimeWorkRequest 和 Worker。如需处理加急作业,请对 OneTimeWorkRequest 调用 setExpedited()。 |
---|---|---|
长时间运转 | 一次性或守时 | 任意 WorkRequest 或 Worker。在作业器中调用 setForeground() 来处理告知。 |
可延期 | 一次性或守时 | PeriodicWorkRequest 和 Worker。 |
接下来来看详细的运用办法。
3、入门运用
3.1 增加依靠库
本文代码运用Kotlin编写,所以这儿仅引进Kotlin相关的库即可,在build.gradle中增加代码如下所示:
def work_version = "2.7.1"
implementation "androidx.work:work-runtime-ktx:$work_version"
假如运用的是Java语言该如何引用呢?听我的,放弃吧~
3.2 界说作业Worker
这儿咱们以上传日志文件使命为例,新建UploadLogWorker类,承继自Worker,代码如下所示:
class UploadLogWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
Log.d("打印线程", Thread.currentThread().name)
return Result.success()
}
}
承继自Worker的类需求重写doWork办法,咱们能够在这个办法中履行详细的使命,这儿为了有演示成果打印出线程的称号。
Result用于回来使命的履行成果Result.success表明履行成功;Result.failure、Result.retry则分别表明履行失利和失利后尝试重试。
3.3 创立使命恳求WorkRequest
这儿咱们创立一个一次性的履行使命,代码如下所示:
val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
.build()
3.4 将使命提交体系
创立好使命之后,就能够将使命提交体系,履行恳求,代码如下所示:
WorkManager.getInstance(this).enqueue(uploadLogWorkerRequset)
运转App,运转成果如下图所示。
3.5 为使命传递参数
许多时分咱们在履行使命的时分是需求参数的,比方上传日志文件咱们要知道日志文件的途径或者其他参数,咱们怎么样将参数传递给Worker呢?
咱们能够经过WorkRequest的setInputData办法来设置参数,代码如下所示:
val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
.setInputData(workDataOf( "filePath" to "file://***" , "fileName" to "log.txt" ))
.build()
这儿咱们传递了文件途径filePath和文件名fileName,在Worker经过getInputData办法承受,比方咱们在doWork中承受参数并打印。代码如下所示:
override suspend fun doWork(): Result {
val filePath = inputData.getString( "filePath" )
val fileName = inputData.getString( "fileName" )
Log.d( "承受的参数" , " $fileName : $filePath " )
return Result.retry()
}
运转程序,打印如下图所示。
这样咱们就完成了一个最简单的WorkManager运用案例。接着咱们来进一步的探究。
4、履行加急作业你所需求知道的
从 WorkManager 2.7 开端,咱们能够调用setExpedited办法来告知体系,我这个使命是加急使命,请赶快履行。修正代码如下所示:
val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
.setExpedited(OutOfQuotaPolicy. RUN_AS_NON_EXPEDITED_WORK_REQUEST )
.build()
setExpedited办法中的OutOfQuotaPolicy参数有两个枚举值,含义如下所示。
枚举值 | 含义 |
---|---|
RUN_AS_NON_EXPEDITED_WORK_REQUEST | 当体系无法为使命加急处理时,使命变成常规使命 |
DROP_WORK_REQUEST | 当体系无法为使命加急处理时,删除改使命 |
所以咱们这儿声明为RUN_AS_NON_EXPEDITED_WORK_REQUEST即可。再次运转程序。
OK,完美运转???
不过我的手机是Android 12的,为了确保没问题,咱们有必要在Android 11 或低版本上履行一次。没崩溃,但是使命却没履行,咱们看到了过错日志如下图所示。
Emm.. 一堆乱七八糟的,要害信息在这句话
Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
从官方咱们获取到了这些信息:在 Android 12 之前,作业器中的 getForegroundInfoAsync()
和 getForegroundInfo()
办法可让 WorkManager 在您调用 setExpedited()
时显示告知。假如您想要恳求使命作为加急作业运转,则一切的 ListenableWorker 都有必要完成 getForegroundInfo
办法。
假如未能完成对应的 ****getForegroundInfo
办法,那么在旧版平台上调用 setExpedited
时,或许会导致运转时崩溃。
了解到了这些,那咱们就来完成getForegroundInfo()
办法,修正UploadLogWorker代码如下所示:
classUploadLogWorker(context:Context,workerParams:WorkerParameters):
Worker(context,workerParams){
overridefundoWork():Result{
Log.d("打印线程",Thread.currentThread().name)
setForegroundAsync(getForegroundInfo())
returnResult.success()
}
@SuppressLint("RestrictedApi")
overridefungetForegroundInfoAsync():ListenableFuture<ForegroundInfo>{
valfuture=SettableFuture.create<ForegroundInfo>()
future.set(getForegroundInfo())
returnfuture
}
fungetForegroundInfo():ForegroundInfo{
valnotificationManager=
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE)asNotificationManager
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
valchannel=NotificationChannel(
"1",
"hh",
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
valnotification=NotificationCompat.Builder(applicationContext,"1")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle(applicationContext.getString(R.string.app_name))
.setContentText("我是一个上传日志的使命")
.build()
returnForegroundInfo(1337,notification)
}
}
再次在Android11 上运转程序,发现打印出了日志,并显示了一个使命告知,如下图所示。
这一点是在履行加急作业时所有必要要注意的。
5、协程作业CoroutineWorker
1、将承继类修正为CoroutineWorker
2、完成getForegroundInfo办法,内容与上getForegroundInfo共同
6、守时使命PeriodicWorkRequest
在3.2中咱们界说了一次性使命OneTimeWorkRequestBuilder,现在咱们将上传日志的这个使命修正为守时使命,代码如下所示:
val uploadLogWorkerRequset: WorkRequest = PeriodicWorkRequestBuilder<UploadLogWorker>(15,TimeUnit.MINUTES)
.build()
这儿指定了,守时使命的周期是15分钟一次,能够界说的最短重复距离便是 15 分钟,这一点开发者在测验的时分需求注意,不能傻傻的等着…,这儿我就傻傻的等了15分钟,确保守时使命是能够履行的。
7、作业束缚、延迟履行和重试策略
7.1 作业束缚
很多情况下,咱们需求为使命增加作业束缚,比方上传日志的使命肯定是在有网络的条件下进行的,当时支撑的束缚条件如下所示。
NetworkType | 束缚运转作业所需的网络类型。例如 Wi-Fi (UNMETERED)。 |
---|---|
BatteryNotLow | 假如设置为 true,那么当设备处于“电量缺乏形式”时,作业不会运转。 |
RequiresCharging | 假如设置为 true,那么作业只能在设备充电时运转。 |
DeviceIdle | 假如设置为 true,则要求用户的设备有必要处于空闲状态,才能运转作业。在运转批量操作时,此束缚会十分有用;若是不用此束缚,批量操作或许会下降用户设备上正在积极运转的其他运用的功能。 |
StorageNotLow | 假如设置为 true,那么当用户设备上的存储空间缺乏时,作业不会运转。 |
比方咱们现在为一次性使命增加束缚为在链接wifi的情况下履行,首先用Constraints构建一个束缚实例能够将多个束缚条件放在一同。代码如下所示:
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.build()
这儿设置为仅在充电的时分履行。接着为使命构建器增加束缚。
val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
.setConstraints(constraints)
.build()
这样一来使命就会在仅充电的时分履行了。
7.2 延迟履行
延迟履行适用于一次性使命和守时使命,但运用在守时使命事仅对第一次履行有用,为啥呢?因为是守时使命呀~
咱们为一次性使命设置延迟时间为5秒钟,代码如下所示:
val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
.setConstraints(constraints)
.setInitialDelay( 5 ,TimeUnit.SECONDS)
.build()
运转程序,能够看到5秒钟后,程序才打印了日志,这儿就不演示了。
7.3 重试策略
在3.2中界说Work中咱们提到了Result.retry能够让使命重试,咱们也能够自界说使命的重试策略和退避政策,咱们经过详细的例子来解说。
val uploadLogWorkerRequset: WorkRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL, OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
最短退避延迟时间设置为允许的最小值,即 10 秒。因为政策为 LINEAR,每次尝试重试时,重试距离都会增加约 10 秒。例如,第一次运转以 Result.retry() 完毕并在 10 秒后重试;然后,假如作业在后续尝试后继续回来 Result.retry(),那么接下来会在 20 秒、30 秒、40 秒后重试,以此类推。
打印日志如下图所示。
咱们能够看到,第一次使命失利后延迟了10秒从头履行,第2次延迟了20秒,第三次延迟了40秒…
8、调查作业履行成果
在使命完成后,我或许需求进行更新UI或者事务逻辑操作。咱们能够经过注册监听器来调查 WorkInfo 的变化,以依据ID查询WorkInfo状态为例,代码如下所示:
WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadLogWorkerRequset.id).observe(this){
if (it.state == WorkInfo.State.SUCCEEDED){
Toast.makeText(this,"使命履行成功,更新UI",Toast.LENGTH_LONG).show()
}else{
//使命失利或重试
}
}
除了getWorkInfoByIdLiveData之外还有依据tag、name等查询的转化办法,这儿读者可自行检查API。
运转程序,成果如下图所示。
类似的咱们还能够经过cancelWorkById等办法来撤销使命的履行。这儿不做演示了。
9.总结
9.1 特性
- 在早于 Android 12 的 API 版本中,加急作业都是由前台服务履行的,而从 Android 12 开端,它们将由加急作业 (expedited job) 完成。所以在第4小节中,Android12上并不会显示告知栏
- WorkManager 仅仅一个处理守时使命的东西
- WorkManager 最早兼容到 API 14(Android 4.0)
9.2 注意事项
- 运用WorkManager注册的周期性使命不能确保一定会准时履行,这并不是bug,而是系 统为了减少电量消耗,或许会将触发时间接近的几个使命放在一同履行,这样能够大幅度地减 少CPU被唤醒的次数,然后有用延伸电池的运用时间。
- WorkManager官方虽然称它能够确保即便在运用退出乃至手机重启的情况下,之前注册的使命依然将会得到履行。但是在国产手机中是不或许的,因为体系自己做了改动。但是在国产机上测验退出后,再进来也会履行之前的使命。这个时分或许就会有重复的使命履行。
- 假如使命现已开端履行调用撤销使命的办法是无法终止使命的,但是调用撤销办法之后,无法再调查到使命成果。
- 履行一个后台使命,在使命完毕前杀死APP,再次进来时之前未完成的使命会从头开端履行,且履行完毕后无法收到回调。
- 使命增加到行列后,未开端履行前,假如是在onDestory中调用撤销使命的办法是不可行的,此种情况下下次进来时依然会有重复使命开端履行。(原生体系、国产机一样)
9.3 在事务中运用需求重视的问题
- 使命增加到行列后,未开端履行前,假如是在onDestory中调用撤销使命的办法是不可行的,此种情况下下次进来时依然会有重复使命开端履行。
产生原因:cancelWork操作是一个异步操作,调用此操作后撤销操作还未履行完毕进程便完毕了
事务影响: 连续打开关闭屡次,会有多个重复的使命履行,且之前的使命无法收到使命进度回调
解决方案: 暂无