Android的后台机制是一个很复杂的问题。印象中与后台相关的API变更大概是这些:从4.4系统开始AlarmManager的触发时间由原来的精准变为不精准,5.0系统中加入了JobScheduler来处理后台任务,6.0系统中引入了Doze和App Standby模式用于降低手机被后台唤醒的概率,从8.0系统开始直接禁用了Service的后台功能,只允许使用前台Service。当然,还有许许多多小细节的修改。
这么频繁的功能和API变更,让开发者就很难受了,到底该如何编写后台代码才能保证应用程序在不同系统版本上的兼容性呢?为了解决这个问题,Google推出了WorkManager组件。**WorkManager很适合用于处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择底层是使用AlarmManager实现还是JobScheduler实现,从而降低了我们的使用成本。**另外,它还支持周期性任务、链式任务处理等功能,是一个非常强大的工具。
不过,我们要明确一件事情:WorkManager和Service并不相同,也没有直接的联系。Service是Android系统的四大组件之一,它在没有被销毁的情况下是一直保持在后台运行的。而WorkManager只是以个处理定时任务的工具,它可以保证即使在应用退出甚至手机重启的情况下,之前注册的任务仍然将会得到执行。因此,WorkManager很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据,等等。
另外,使用WorkManager注册地周期性任务不能保证一定会准时执行,这并不是bug,而是系统为了减少电量消耗,可能会将触发事件临近地几个任务放在一起执行,这样可以大幅度地减少CPU被唤醒地次数,从而有效延长电池地使用时间。
WorkManager的基本用法
使用WorkManager,首先需要在app/build.gradle文件中添加如下依赖:
dependencies {
implementation 'androidx.work:work-runtime:2.7.0-alpha01'
}
将依赖添加完成之后,我们就把准备工作做好了。
WorkManager的基本用法,主要分为3步:
- 定义一个后台任务,并实现具体的任务逻辑;
- 配置该后台任务的运行条件和约束信息,并构建后台任务请求;
- 将该后台任务请求传入WorkManager的enqueue方法中,系统会在合适的时间运行。
示例如下:
package com.example.permissionx
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
class SimpleWorker(context:Context,params:WorkerParameters):Worker(context,params) {
//doWork方法不会运行在主线程中,可以在这个方法中处理耗时逻辑
//doWork方法要求返回一个Result对象,用于表示任务的运行结果,
// 成功就返回 Result.success(),失败就返回 Result.failure()。
// 除此之外,还有 Result.retry()方法,它其实也代表着失败,只是可以结合setBackoffCriteria方法重新执行任务。
override fun doWork(): Result {
Log.e("SimpleWorker","do work in SimpleWorker")
return Result.success()
}
}
package com.example.permissionx
import android.Manifest
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.permission.brettdev.PermissionX
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
doWorkBtn.setOnClickListener {
//OneTimeWorkRequest用于构建单次运行的后台任务请求
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(10,TimeUnit.SECONDS)//让后台任务在指定的延迟时间后运行
.addTag("simple")
//给后台任务请求添加标签,添加了之后可以通过标签取消后台任务请求.例如: WorkManager.getInstance(this).cancelAllWorkByTag("simple"),
// 如果不设置Tag可以通过 WorkManager.getInstance(this).cancelWorkById(request.id)取消,也可以通过WorkManager.getInstance(this).cancelAllWork()取消所有的后台任务请求
.build()
WorkManager.getInstance(this).enqueue(request)
}
}
}
使用WorkManager处理复杂的任务
如果后台任务的doWork方法中返回了Result.retry方法,那么可以结合setBackoffCriteria方法来重新执行任务的,具体代码如下:
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(10,TimeUnit.SECONDS)//让后台任务在指定的延迟时间后运行
.setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.SECONDS)
.....
.build()
setBackoffCriteria方法接收3个参数:第二个和第三个参数用于指定在多久之后重新执行任务,时间最短不能少于10秒;第一个参数则用于指定如果任务再次执行失败,下次重试的时间应该以什么样的形式延迟。这其实很好理解,假如任务一直执行失败,不断重新执行似乎没有什么意义,只会增加设备的性能消耗。而随着失败的次数增多,下次重试的时间应该延迟,这才是合理的机制。第一个参数的可选值有两种:LINEAR和EXPONENTIAL,前者代表下次重试时间以线性的方式延迟,后者代表下次重试时间以指数的方式延迟。
通过如下代码对doWork返回的结果进行监听:
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
.observe(this,object :Observer<WorkInfo>{
override fun onChanged(t: WorkInfo?) {
if(t!!.state == WorkInfo.State.SUCCEEDED){
//....
}else if(t.state == WorkInfo.State.FAILED){
//...
}
}
})
//另外,也可以调用getWorkInfoByTagLiveData方法,监听同一标签下的所有后台任务请求的运行结果,用法和上面差不多。
- 链式任务
比如有四个任务A,B,C,D,我们需要先执行完A和B两个任务,在执行C任务,最后在执行D任务,代码如下:
WorkManager.getInstance()
// 先执行 A,B,并行执行
.beginWith(Arrays.asList(A, B))
// 等 A 和 B 都执行完成后,再执行 C
.then(C)
// 等C执行完,再执行 D
.then(D)
// Don't forget to enqueue()
.enqueue();
workmanager要求,必须在前一个后台任务运行成功之后,下一个后台任务才会运行。也就是说,如果某个后台任务运行失败或者被取消了,那么接下来的后台任务就都得不到运行了。
最后注意,workmanager的所有功能在国产手机上都有可能得不到正确的运行。这是因为绝大多数国产手机厂商在进行Android系统定制的时候会增加一个一键关闭的功能,允许用户一键杀死所有非白名单的应用程序。而被杀死的应用程序既无法接收广播,也无法运行workmanager的后台任务。因此,workmanager可以用,但是不要依赖它去实现什么核心功能,因为在国产手机上很不稳定。