1. 简介
当我们想要实现不是很紧急但是需要必须完成的处理时会比较难办,如果使用service
会产生额外的电量消耗,如果使用Broadcast
比较难实现以及还需要设置触发条件。为了解决这个问题谷歌在Android 5.0
时推出了JobScheduler
,来替换此前的方案。
作为更进一步在Android Jetpack
上推出了WorkManager
, 它会根据系统的版本用不同的方法去实现上述需求。
2. WorkManager的简单介绍
WorkManager 是一个 Android 库, 它在工作的触发器 (如适当的网络状态和电池条件) 满足时, 优雅地运行可推迟的后台工作。WorkManager 尽可能使用框架 JobScheduler , 以帮助优化电池寿命和批处理作业。在 Android 6.0 (API 级 23) 下面的设备上, 如果 WorkManager 已经包含了应用程序的依赖项, 则尝试使用Firebase JobDispatcher 。否则, WorkManager 返回到自定义 AlarmManager 实现, 以优雅地处理您的后台工作。
根据官方的叙述,WorkManager
会根据限制条件自动进行后台任务。如果系统版本在6.0以上则会使用JobScheduler
,如果是一下则会使用AlarmManager+BroadCastReceiver
。
3. 使用方法
3.1 添加依赖
在module的build.gradle
中添加如下依赖。
implementation "androidx.work:work-runtime-ktx:2.3.3"
3.2 创建后台处理
我们首先需要创建需要被执行的任务。我们需要继承Worker
类,并且需要重写doWork
方法。
class CustomWorker(context: Context, workerParameters: WorkerParameters) :
Worker(context, workerParameters) {
override fun doWork(): Result {
Log.d("CustomWorker", "Worker is active in " + inputData.getString("data"))
return Result.success()
}
}
inputData.getString(key)
是获取外部的传值。使用方法跟intent
传值类似。
我们需要返回的值是Result
的枚举值。一共有如下几种。
Result.success()
表示任务执行成功。Result.retry()
表示任务执行失败,需要再次尝试。可以在后面讲述的退避规则BackoffPolicy
的设定方法进行尝试。Result.failure()
表示任务执行失败,但不再进行尝试。
3.3 创建触发时的限制条件
我们需要通过Constrains
来设置触发时的限制条件。
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.build()
3.3.1 setRequiredNetworkType
设置需要的网络类型。一共有如下几种。
枚举值 | 状态说明 |
---|---|
NOT_REQUIRED | 不需要网络 |
CONNECTED | 任何可用网络 |
UNMETERED | 需要不计量网络,如WiFi |
NOT_ROAMING | 需要非漫游网络 |
METERED | 需要计量网络,如4G |
3.3.2 setRequiresBatteryNotLow
设置手机电量的状态,这里的条件是执行任务时手机电量不能较低。
3.3.3 setRequiresStorageNotLow
设置手机内存空间的状态,这里的条件是执行任务时手机内存空间不能较低。
3.3.4 setRequiresCharging
设置手机的充电状态, 这里的条件是手机执行任务时手机是处于充电的状态。
3.3.5 setRequiresDeviceIdle
设置手机的状态, 这里的条件是手机执行任务时手机是出于空闲状态。
3.3.6 addContentUriTrigger(uri:Uri, triggerForDescendants:Boolean)
添加触发器,即当指定的URI中有内容更新时会触发任务。值得注意的是这里只能在OneTimeWorkRequest
中设置。
3.4 创建WorkRequest
一共有两种WorkRequest
。一种是只执行一次的OneTimeWorkRequest
,还有一种是定期执行的PeriodicWorkRequest
。
val oneTimeWorkRequest =
OneTimeWorkRequestBuilder<CustomWorker>().setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.setInputData(inputData)
.build()
val periodicWorkRequest =
PeriodicWorkRequestBuilder<CustomWorker>(10, TimeUnit.MILLISECONDS)
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
PeriodicWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
).build()
在PeriodicWorkRequest
的构造器中还需要添加执行的周期,上述代码是每10秒去确认条件是否执行。但是周期任务最少间隔是15分钟
,所以虽然是写了10秒,但是会在15分钟后会被执行。
3.4.1 setConstraints
我们要在WorkRequest
中设置Constraints
(即上面讲过的限制条件)。
3.4.2 setBackoffCriteria
我们还可以设置退避条件。 退避条件有两个属性:
BackoffPolicy
, 默认为是指数性的,但是可以设置成线性。- 持续时间, 默认为30秒。
3.4.3 setInputData
我们可以在WorkRequest
中通过setInputData
方法给Worker
传值。使用方法跟Intent
相似,如下。
val inputData = Data.Builder().putString("data", "MainActivity").build()
3.5 通过WorkManager执行
最后我们可以通过WorkManager
去执行上面制定的各种需求。
WorkManager.getInstance(this).enqueue(oneTimeWorkRequest)
3.6 监视Worker执行情况
我们还可以通过LiveData
对Worker
的执行情况进行监视。当Worker
被执行时,监视处会受到通知。
WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.id).observe(this,
Observer {
count++
txtId.text = "Work status is changed! $count"
Toast.makeText(this, "Work status is changed!",Toast.LENGTH_LONG).show()
})
WorkInfo
的源代码如下:
private @NonNull UUID mId;
private @NonNull State mState;
private @NonNull Data mOutputData;
private @NonNull Set<String> mTags;
private @NonNull Data mProgress;
private int mRunAttemptCount;
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public WorkInfo(
@NonNull UUID id,
@NonNull State state,
@NonNull Data outputData,
@NonNull List<String> tags,
@NonNull Data progress,
int runAttemptCount) {
mId = id;
mState = state;
mOutputData = outputData;
mTags = new HashSet<>(tags);
mProgress = progress;
mRunAttemptCount = runAttemptCount;
}
首先看一下State
。
public enum State {
ENQUEUED,//加入队列
RUNNING,//运行中
SUCCEEDED,//已成功
FAILED,//失败
BLOCKED,//挂起
CANCELLED;//取消
public boolean isFinished() {
return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
}
}
BLOCKED
的情况是,当约束情况没有被全部满足是Worker
会被挂起。
3.7 结束任务
当WorkRequest
入列了以后,WorkManager
会为它分配一个work ID
,我们可以通过work ID
进行任务的取消或停止。
WorkManager.getInstance(this).cancelWorkById(workRequest.id)
WorkManager并不一定能结束任务,因为有些任务可能已经执行完毕 除了上述方法以外还有其他结束任务的方法:
cancelAllWork()
: 取消所有的任务cancelAllWorkByTag(tag:String)
: 取消一组带有相同标签的任务cancelUniqueWoork(uniqueWorkName:String)
: 取消唯一任务
3.8 链式调用
当我们需要按顺序或者同时执行WorkRequest
时,我们可以用链式调用的方法执行。
3.8.1并行执行
val workRequest1 = OneTimeWorkRequestBuilder<CustomWorker>().build()
val workRequest2 = OneTimeWorkRequestBuilder<CustomWorker>().build()
val workRequest3 = OneTimeWorkRequestBuilder<CustomWorker>().build()
// 会同时执行。
WorkManager.getInstance().beginWith(workRequest1,workRequest2,workRequest3).enqueue()
3.8.2顺序执行
我们可以使用beginWith-then
方法进行顺序执行。如果顺序执行的途中有一个失败了,之后的任务将不会被执行。
// 顺序执行
WorkManager.getInstance().beginWith(workRequest1).then(workRequest2).then(workRequest3).enqueue()
3.8.3 组合执行
我们可以使用combine
操作符进行组合执行。
// 组合执行
val chain1 = WorkManager.getInstance()
.beginWith(workRequest1)
.then(workRequest2)
val chain2 = WorkManager.getInstance()
.beginWith(workRequest3)
.then(workRequest4)
WorkContinuation
.combine(chain1, chain2)
.then(workRequest5)
.enqueue()
3.8.4 唯一链
唯一链,如其名就是同一时间内在执行队列中农不能存在相同名称的任务。
val workRequest = OneTimeWorkRequestBuilder<CustomWorker>().build()
WorkManager.getInstance(this).beginUniqueWork("TAG", ExistingWorkPolicy.REPLACE, workRequest)
注意的是最有一个参数WorkRequest
可变长度的参数。
第一个参数就是任务名字"TAG"。
第二个参数是已存在任务时的执行策略。枚举值如下。
public enum ExistingWorkPolicy {
REPLACE, // 替换
KEEP, // 保持,不做任何操作
APPEND // 添加到已有的任务中
}
REPLACE
: 存在相同名称的任务并且被挂起,则取消和删除现有的任务,然后替换新的任务。KEEP
: 存在相同名称的任务并且被挂起,则不做任何操作。APPEND
: 存在相同名称的任务并且被挂起,会把新任务添加到缓存中,都队列中的所有任务都被执行完毕时,新任务会被设为第一任务。
github: github.com/HyejeanMOON…