本文主要讲述在Service中使用WorkManager执行周期性任务遇到的问题和解决方法。
大屏物联网设备上两个应用A和B,A应用负责业务,随系统自启动(始终位于前台),B应用作为A应用的守护进程(始终处于后台运行状态),负责后台安装A应用下载后的安装包以及周期性的执行一些任务。
dependencies { def work_version = "2.3.1" // (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" } 复制代码
根据自己需求声明相应的依赖项
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 task finished successfully with the Result return Result.success() } } 复制代码
官方给出的示例如上,达到执行条件后,doWork()将会被执行。我们可以根据任务的执行情况返回相应状态结果,Result有三种状态结果可以返回:当任务正常执行完毕时,我们返回Result.success(outputData),执行中出现异常时根据实际情况返回Result.failure(outputData)或Result.retry()。
例如:
... override fun doWork(): Result { uploader.uploadImages(object: Callback{ override fun onSuccess(data: String){ ... } override fun onFailed(){ ... } }) return ?? } 复制代码
我们可以使用CountDownLatch或CyclicBarrier同步锁机制将异步回调同步处理,在周期性任务情况下,考虑到复用性,以CyclicBarrier为例,如下:
private val cyclicBarrier = CyclicBarrier(2) private var workResult: Result = Result.success() uploader.uploadImages(object: Callback{ override fun onSuccess(data: String){ ... workResult = Result.success(outputData) cyclicBarrier.await() } override fun onFailed(){ ... workResult = Result.failure(outputData) cyclicBarrier.await() } }) cyclicBarrier.await(5,TimeUnit.MINUTES) //设置等待uploadImages返回结果最多5分钟 return workResult 复制代码
如下,定义一个只有在有可用网络的情况下重复间隔为一小时的定期工作请求:
val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() val saveRequest = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS) .setConstraints(constraints) .build() WorkManager.getInstance(myContext) .enqueue(saveRequest) 复制代码
:warning:注意:可以定义的最短重复间隔是 15 分钟(与 JobScheduler API 相同),工作器的确切执行时间取决于在工作请求中使用的约束,也取决于系统进行的优化。实测,在Android 7.1原生系统,在满足约束的情况下,大多数时候实际执行的周期与设置的周期相差在2分钟以内,WorkManager并不适合用于对执行时间精确度要求高的情况。
如上,调用WorkManager的enqueue(saveRequest)将工作请求放入队列,如果此时满足设置的约束,将会执行Worker中doWork(),否则会等到满足约束条件开始执行。
经过前边两步,我们设置约束和周期的后台任务已经开始执行,如果想要获取工作状态和工作进度、结果,还要通过id或tag获取到对应的WorkRequest并注册监听器,如下:
WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(uploadWorkRequest.id) .observe(lifecycleOwner, Observer { workInfo -> if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) { displayMessage("Work finished!") } }) 复制代码
:warning:注意:如果工作器返回 Result.success(),则被视为处于 SUCCEEDED 状态,相反,如果工作器返回 Result.failure(),则被视为处于 FAILED 状态。SUCCEEDED和FAILED为工作终止状态,只有OneTimeWorkRequest可以进入终止状态,也就是只有OneTimeWorkRequest持有Worker时,才可以获取到Result.success(outputData)和Result.failure(outputData)中返回的outputData
WorkManager 2.3.0-alpha01 为设置和观察工作器的中间进度添加了一流支持。如果应用在前台运行时,工作器保持运行状态,也可以使用返回 WorkInfo 的 LiveData 的 API 向用户显示此信息。
ListenableWorker 现在支持 setProgressAsync() API,此类 API 可以保留中间进度。借助这些 API,开发者能够设置可通过界面观察到的中间进度。进度由 Data 类型表示,这是一个可序列化的属性容器(类似于 input 和 output,并且受到相同的限制)。
由此可知,在Worker中可以通过setProgressAsync()来设置进度和数据。setProgressAsync()是一个异步操作方法,会调用ProgressUpdater的updateProgress()操作数据库,所以当doWork()中的异步任务完成时,如果调用setProgressAsync()设置进度结果后直接return Result.success()或Result.failure()时将获取不到通过setProgressAsync()设置的数据,因为这个时候PeriodicWorkRequest的WorkInfo状态是ENQUEUED。
:warning:注意:只有WorkInfo的状态为RUNNING时才可以获取到setProgressAsync()设置的进度数据。
代码如下:
class UpgradeFirmwareWorker(appContext: Context,workerParams: WorkerParameters): Worker(appContext,workerParams) { private var resultMessage = "" private var running = AtomicBoolean(false) private val outData = Data.Builder() private val cyclicBarrier = CyclicBarrier(2) private var workResult: Result = Result.success() override fun doWork(): Result { setProgressAsync(outData.putString(KEY_RESULT_MESSAGE,"正在检查升级").build()) if (running.compareAndSet(false,true)) { MTAUpdate.checkUpdate(object : UpdateListener { override fun onFailed(e: UpdateException, firmwareName: FirmwareName) { running.set(false) resultMessage = "result:${e.message},firmwareName:$firmwareName,running:${running.get()}" setProgressAsync(outData.putString(KEY_RESULT_MESSAGE,e.message).build()) workResult = Result.failure() Thread.sleep(1000) cyclicBarrier.await() } override fun onSuccess(result: UpdateResult) { running.set(false) resultMessage = result.toString()+",running:${running.get()}" setProgressAsync(outData.putString(KEY_RESULT_MESSAGE,result.toString()).build()) workResult = Result.success() Thread.sleep(1000) cyclicBarrier.await() } }) try { cyclicBarrier.await(5,TimeUnit.MINUTES) } catch (e: InterruptedException){ e.printStackTrace() } catch (e: TimeoutException){ logi("等待超时,checkUpdate未返回结果") } } else { logd("UpgradeFirmwareWorker","正在检查升级") } return workResult } companion object{ const val KEY_RESULT_MESSAGE = "result_message" } } 复制代码
上边的代码,在调用setProgressAsync()后使用Thread.sleep(1000)暂停一秒,确保不会立即调用return Result.success()或Result.failure(),这样在监听器中通过判断WorkInfo.State == RUNNING时,调用WorkInfo的getProgress()可以获取到工作进度。
Service中代码如下:
WorkManager.getInstance(applicationContext).apply { enqueueUniquePeriodicWork(REQUEST_TAG,ExistingPeriodicWorkPolicy.KEEP,upgradeRequest) getWorkInfoByIdLiveData(upgradeRequest.id) .observe(this@URMService, Observer { if (it != null && it.state == WorkInfo.State.RUNNING){ val progress = it.progress val value = progress.getString(UpgradeFirmwareWorker.KEY_RESULT_MESSAGE) progressListener?.onProgress(value) } }) } 复制代码
enqueueUniquePeriodicWork()作用是确保队列中工作请求的唯一性,第二个参数ExistingPeriodicWorkPolicy.KEEP指定当队列中有同样的工作请求时忽略新的请求,保持已有工作继续执行。
前边提到观察工作状态可以通过getWorkInfoByIdLiveData()或getWorkInfosByTagLiveData()获取工作请求,此时返回持有WorkInfo的LiveData,LiveData的observe(lifecycleOwner,observer)需要传入lifecycleOwner(生命周期的持有者)和observer(观察者)。
在androidx.appcompat:appcompat兼容库中已经提供了实现了LifecycleOwner接口的AppCompatActivity和Fragment,在Service中我们需要自行实现LifecycleOwner,绑定Service的生命周期,具体实现如下:
class URMService : Service(),LifecycleOwner { private lateinit var lifecycleRegistry: LifecycleRegistry private lateinit var upgradeRequest: PeriodicWorkRequest override fun onCreate() { super.onCreate() lifecycleRegistry = LifecycleRegistry(this) lifecycleRegistry.currentState = Lifecycle.State.CREATED val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() upgradeRequest = PeriodicWorkRequestBuilder<UpgradeFirmwareWorker>(20,TimeUnit.MINUTES) .addTag(REQUEST_TAG) .setConstraints(constraints) .build() WorkManager.getInstance(applicationContext).apply { enqueueUniquePeriodicWork(REQUEST_TAG,ExistingPeriodicWorkPolicy.KEEP,upgradeRequest) getWorkInfoByIdLiveData(upgradeRequest.id) .observe(this@URMService, Observer { if (it != null && it.state == WorkInfo.State.RUNNING){ val progress = it.progress val value = progress.getString(UpgradeFirmwareWorker.KEY_RESULT_MESSAGE) progressListener?.onProgress(value) } }) } } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { lifecycleRegistry.currentState = Lifecycle.State.STARTED return START_STICKY } override fun onBind(intent: Intent): IBinder? { return binder } override fun onDestroy() { lifecycleRegistry.currentState = Lifecycle.State.DESTROYED WorkManager.getInstance(applicationContext).cancelAllWork() } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } } 复制代码
LifecycleOwner接口只有一个getLifecycle(),该方法返回一个代表Android生命周期的Lifecycle对象。Lifecycle是一个抽象类,在androidx.lifecycle包中已经为我们提供了Lifecycle的实现类LifecycleRegistry,androidx.appcompat:appcompat兼容库中提供的Activity和Fragment也是使用了LifecycleRegistry。我们只需要实例化LifecycleRegistry,然后在Service的onCreate()、onStartCommand()、onDestroy()中调用LifecycleRegistry的setCurrentState(State state)传入对应的Lifecycle State即可。