WorkManager简介
使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。
功能特点
-
向后兼容到API14,在API23以上调用JobScheduler,在API14-22上使用BroadcastReceiver 和 AlarmManage WorkManager API在不同SDK下的实现原理
-
可添加工作约束条件(即在什么条件下任务可以得以执行)约束条件包含有网条件下,充电条件下,电量充足条件下执行 指定工作的执行环境
-
可以执行一次性任务或者周期性任务 指定工作执行次数
-
可以返回监控任务的进度,执行结果 进行任务状态监控
-
将任务链接起来 流水线工作,指定工作流程与顺序
使用场景
旨在用于可延迟运行(即不需要立即运行)并且在应用退出或设备重启时必须能够可靠运行的任务。例如
1,向后端服务发送日志或分析数据
2,定期将应用数据与服务器同步
3,下载资源与或者更新APK
WorkManager使用步骤
WorkManager的使用基本可以按照以下步骤进行
- 继承Work(同步)或者RxWork(异步)类,并将要执行的工作内容进行编写
- 创建WorkRequest,并指定工作约束条件,如有网状态下,充电状态下等
- 调用WorkManager的enqueu()方法将上述的WorkRequest添加到系统队列中
WorkManager-API相关类
Work&RxWork &CustomWork(自定义异步Worker)
继承Work
class TestWork(appContext: Context, workerParams: WorkerParameters) :Worker(appContext,workerParams){
override fun doWork(): Result {
//TODO 进行同步任务
/*
* Result.success() 任务执行成功
* Result.failure() 任务执行失败
* Result.retry() 申请任务重新执行
*/
return Result.retry()
}
}
继承RxWork配合RxJava进行异步任务
class TestRxWork(appContext: Context, workerParams: WorkerParameters) : RxWorker(appContext,workerParams){
override fun createWork(): Single<Result> {
return Observable.range(0, 1)
/**
* Retriving all shows in local DB
*/
.flatMapSingle {
getAllshowsFromDB()
}
/**
* Filtering update required shows
*/
.map{
arraylistShowToUpdate = filterShowsToBeUpdated(it)
}
/**
* Trigger update to remote server only if there any
* shows required to sync
*/
.filter{
return@filter arraylistShowToUpdate.size > 0
}
/**
* Updateing shows to remote server
*/
.flatMapSingle {
updateShowsToServer(arraylistShowToUpdate)
}
.toList()
/**
* returning sucess
*/
.map {
Result.success()
}
/**
* returning failure
*/
.onErrorReturn {
Result.failure()
}
}
}
自定义异步任务(Worker)
继承 ListenableWorker,实现一个异步任务的Work类型
class DownloadWorkOrder(context: Context,workParams:WorkerParameters): ListenableWorker(context,workParams) {
private lateinit var mFuture: SettableFuture<Result>
override fun startWork(): ListenableFuture<Result> {
Log.d(DEBUG_TAG,"开始执行任务")
mFuture = SettableFuture.create()
return mFuture
}
}
WorkRequest
通过OneTimeWorkRequestBuilder来传入参数与工作约束条件,具体代码如下所示
fun configDownloadBuilder(downloadUrl:String,isApk:Boolean):WorkRequest{
//定义下载地址
val downloadData = workDataOf(KEY_DOWNLOAD_URL to downloadUrl, KEY_IS_APK to isApk)
//开始任务配置--定义输入输出
val builder = OneTimeWorkRequestBuilder<Worker>()
//设置延时时间
builder.setInitialDelay(10, TimeUnit.MINUTES)
builder.setInputData(downloadData)
//设置任务Tag
builder.addTag("work_tag")
//设置约束条件
val constraints = Constraints.Builder()
/*
* 设置网络条件
* NetworkType.NOT_REQUIRED 不需要网络连接
* NetworkType.CONNECTED 任何网络连接
* NetworkType.UNMETERED 未计量的网络连接
* NetworkType.METERED 计量网络连接
*/
constraints.setRequiredNetworkType(NetworkType.CONNECTED)
/*
* 充电状态下
*/
constraints.setRequiresCharging(true)
/*
* 内存不要太小
*/
constraints.setRequiresStorageNotLow(true)
//设置约束条件
builder.setConstraints(constraints.build())
return builder.build()
}
WorkManager
workManager用于将生成的WorkRequest提交至系统,系统在添加到系统维护的任务队列当中去,当满足约束条件,且当前应用启动的时候,开始执行Work中的任务列表,并且可以通过WorkRequest的ID值对当前Work的工作内容进行监控
将任务提交至系统执行
fun startDownloadRes(workRequest: WorkRequest){
WorkManager.getInstance(mContext).enqueue(workRequest)
}
通过WorkRequestID值进行监控
context?.let {
WorkManager.getInstance(it).getWorkInfoByIdLiveData(mWorkRequest.id)
.observe(this.viewLifecycleOwner, Observer { workInfo ->
if (workInfo != null && workInfo.state == WorkInfo.State.RUNNING) {
//任务执行进度
val process = workInfo.progress
}
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
//任务执行成功
}
if (workInfo != null && workInfo.state == WorkInfo.State.FAILED) {
//任务执行失败
}
if (workInfo != null && workInfo.state == WorkInfo.State.ENQUEUED) {
// 任务等候中
}
})
}
WorkManager任务执行
定义任务
class TestWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result {
//doWork 是在异步线程中执行的,不是主线程
applicationContext.runOnUiThread {
toast("doWork接收到任务")
}
return Result.success()//已成功完成:Result.success() //已失败:Result.failure()//稍后重试:Result.retry()
}
}
单一任务执行
//创建一个最简单的任务
val testWorker = OneTimeWorkRequestBuilder<TestWorker>().build()
//提交任务
WorkManager.getInstance(this).enqueue(testWorker)
周期性任务执行
//需要注意的是,可以定义的最短重复间隔为15分钟,小于15分钟的应用场景此方式并不适合
var testWorker = PeriodicWorkRequestBuilder<TestWorker>(15,TimeUnit.MINUTES).build()
WorkManager.getInstance(this).enqueue(testWorker)
任务链
应用场景:
Android Jetpack - 使用 WorkManager 任务链
WorkManager实战
项目需求<在充电,有网,内存够大的情况下进行资源更新>配合RxDownload进行资源下载
- 要将 WorkManager 库导入到 Android 项目中,请将以下依赖项添加到应用的 build.gradle 文件:
dependencies {
def work_version = "2.3.4"
// (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"
}
- 定义工作内容
const val KEY_IS_APK = "KEY_IS_APK"
const val KEY_DOWNLOAD_URL = "key_download_url"
const val DEBUG_TAG = "WORK_MANAGER"
const val DOWNLOAD_TAG = "DOWNLOAD_TAG"
class DownloadWorkOrder(context: Context,workParams:WorkerParameters): ListenableWorker(context,workParams) {
private val mContext = context
private val mParameters = workParams
private lateinit var disposable: Disposable
private lateinit var mFuture: SettableFuture<Result>
override fun startWork(): ListenableFuture<Result> {
Log.d(DEBUG_TAG,"开始执行任务")
mFuture = SettableFuture.create()
val isAPK = inputData.getBoolean(KEY_IS_APK,false)
val url = inputData.getString(KEY_DOWNLOAD_URL)
url?.let {
disposable = url.download()
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {
//设置下载进度
val progress = it.percent()
setProgressAsync(workDataOf("Progress" to progress.toInt()))
Log.d(DEBUG_TAG,"RX_WORK_执行任务进行中:${progress}")
},
onComplete = {
val complete = "资源下载完成,正在解压。。。"
if (isAPK){
//安装APK
Utils.installNormal(mContext,url.file().path)
}else{
//解压文件
val outputPath = "${Environment.getExternalStorageDirectory().absolutePath}/"
try {
doAsync {
ZipUtil.UnZipFolder(url.file().path,outputPath){
mFuture?.set(Result.success())
}
}
}catch (e:IOException){
//错误异常提交重试
mFuture?.set(Result.retry())
Log.d(DEBUG_TAG,"RX_WORK_执行任务失败")
}
Log.d(DEBUG_TAG,"RX_WORK_执行任务成功")
}
},
onError = {
//失败的话提交系统任务重试
mFuture?.set(Result.retry())
//下载安全退出
disposable.safeDispose()
}
)
}
return mFuture
}
}
- 定义任务请求
class DownLoadWorkManager(context: Context){
//定义
private var mContext = context
/*
* 配置下载条件如下
* 1,接收到更新资源的指令
* 2,设备没有被租赁中(空闲状态下)
* 2,充电状态下或者电量大于50%
* 3,网络可用的情况下
* 4,硬盘空间剩余很大
*/
fun configDownloadBuilder(downloadUrl:String,isApk:Boolean):WorkRequest{
//定义下载地址
val downloadData = workDataOf(KEY_DOWNLOAD_URL to downloadUrl, KEY_IS_APK to isApk)
//开始任务配置--定义输入输出
val builder = OneTimeWorkRequestBuilder<DownloadWorkOrder>()
builder.setInputData(downloadData)
//设置任务Tag
builder.addTag(DOWNLOAD_TAG)
//设置约束条件
val constraints = Constraints.Builder()
/*
* 设置网络条件
* NetworkType.NOT_REQUIRED 不需要网络连接
* NetworkType.CONNECTED 任何网络连接
* NetworkType.UNMETERED 未计量的网络连接
* NetworkType.METERED 计量网络连接
*/
constraints.setRequiredNetworkType(NetworkType.CONNECTED)
/*
* 充电状态下
*/
constraints.setRequiresCharging(true)
/*
* 内存不要太小
*/
constraints.setRequiresStorageNotLow(true)
//设置约束条件
builder.setConstraints(constraints.build())
return builder.build()
}
/*
* 开始下载资源
* downloadUrl:下载的网络地址
* downloadPath:资源文件下载的路径
* folder:解压文件夹的名字
*/
fun startDownloadRes(workRequest: WorkRequest){
WorkManager.getInstance(mContext).enqueue(workRequest)
}
}
- 进行代码调用,触发任务执行
//下载链接
val path = ""
//制定任务下载
context?.let {
mWorkRequest = DownLoadWorkManager(it).configDownloadBuilder(path,false)
}
//开始进行下载活动
binding.tvName.setOnClickListener {
context?.let {
DownLoadWorkManager(it).startDownloadRes(mWorkRequest)
}
}
//针对任务进行监控
context?.let {
WorkManager.getInstance(it).getWorkInfoByIdLiveData(mWorkRequest.id)
.observe(this.viewLifecycleOwner, Observer { workInfo ->
if (workInfo != null && workInfo.state == WorkInfo.State.RUNNING) {
val process = workInfo.progress
val value = process.getInt("Progress",0)
val info = "监控---文件下载中,进度:${value}"
it.toast(info)
Log.d(DEBUG_TAG,info)
}
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val info = "监控---文件下载成功"
it.toast(info)
Log.d(DEBUG_TAG,info)
}
if (workInfo != null && workInfo.state == WorkInfo.State.FAILED) {
val info = "监控---文件下载失败"
it.toast(info)
Log.d(DEBUG_TAG,info)
}
if (workInfo != null && workInfo.state == WorkInfo.State.ENQUEUED) {
val info = "监控---文件排队执行中"
it.toast(info)
Log.d(DEBUG_TAG,info)
}
})
}
参考资料
在 WorkManager 中处理异步任务
Android Jetpack - 使用 WorkManager 管理后台任务
WorkManager basics, how to use WorkManager with Rxjava2 & Kotlin Coroutines
Google-中国-WorkManager-视频学习