简介
WorkerManager 适用于执行可以延迟(不需要精确时间)但是必须要稳定执行的的后台任务。适用于向后台同步应用数据,发送日志,应用检查更新等不需要及时完成的后台任务。在本文中这类型的后台任务,命名为延迟后台任务,方便理解。
在Android8.0以后Android系对后台的服务有了严格的限制,因此执行后台任务,需要通过系统调度的方式来执行,Google官方推荐使用JobScheduler 作业替换后台 Service。但是WorkerManager 是JetPack中的一个组成部分,并且完成能够胜任JobScheduler,并且有比JobScheduler 更加好用,因此推荐使用WorkerManager。文章除了介绍WorkerManager的用法,也会将WorkerManager 与 JobScheduler进行对比。
简要使用
后台延迟任务可以分为一次性任务和周期性任务。一次性任务,并不是只执行一次,如果任务没有执行成功,可以进行重试策略,重试策略WorkerManager 和 JobScheduler 的限制条件是一样的,最小的重试时间为10S,最大的重试时间为5小时。
一次性任务
WorkerManager 一次性任务
- 继承Worker。
public class MyWork extends Worker {
private final String TAG = "SecondWork";
public SecondWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Log.d(TAG,"thread id = "+Thread.currentThread().getId());
Log.d(TAG,"执行时间 :"+ DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
return Result.success();
}
}
- 创建WorkerManager,并且将自定义的Worker添加到系统中,等待系统的调度
OneTimeWorkRequest secondWork = new OneTimeWorkRequest.Builder(MyWork .class)
.setInitialDelay(5000,TimeUnit.MILLISECONDS) // 在满足约束条件的前提下,初始延迟时间为5S
.setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.SECONDS) // 重试间隔时间为:curTime + 10 * 重试次数
.build();
WorkManager.getInstance(getContext()).enqueue(secondWork);
Log.d(TAG,"SecondWork 添加到系统 "+ DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
JobScheduler 一次性任务
- 继承JobService。
public class MyService extends JobService{
@Override
public boolean onStartJob(JobParameters params) {
Log.d(TAG,"ThirdService onStartJob="+DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.d(TAG,"ThirdService onStopJob = "+DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
return false;
}
}
- 获取系统调度
jobscheduler
,将自定义的JobService,添加到系统中,开始系统调度。
JobScheduler scheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(getContext(), ThirdService.class);
// 注意jobId
JobInfo.Builder builder = new JobInfo.Builder(1000, jobService);
JobInfo jobInfo = builder
.setMinimumLatency(3*1000)
.setOverrideDeadline(4*1000)
.build();
scheduler.schedule(jobInfo);
周期性任务
WorkerManager 周期性任务
- 继承Worker,这个一步跟一次性任务是一样的。
public class MyWork extends Worker {
private final String TAG = "SecondWork";
public SecondWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Log.d(TAG,"thread id = "+Thread.currentThread().getId());
Log.d(TAG,"执行时间 :"+ DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
return Result.success();
}
}
- 创建WorkerManager,并且将自定义的Worker添加到系统中,等待系统的调度。
PeriodicWorkRequest thirdWork = new PeriodicWorkRequest.Builder(SecondWork.class,
15,TimeUnit.MINUTES) // 周期性任务的间隔时间最小为15分钟
.build();
WorkManager.getInstance(getContext()).enqueue(thirdWork);
Log.d(TAG,"thirdWork 添加到系统 "+ DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
注意,repeatInterval 和 flexInterval 的区别。
JobScheduler 周期性任务
- 继承JobService。
public class MyService extends JobService{
@Override
public boolean onStartJob(JobParameters params) {
Log.d(TAG,"ThirdService onStartJob="+DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.d(TAG,"ThirdService onStopJob = "+DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
return false;
}
}
- 获取系统调度
jobscheduler
,将自定义的JobService,添加到系统中,开始系统调度。
JobScheduler scheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(getContext(), ThirdService.class);
// 注意jobId
JobInfo.Builder builder = new JobInfo.Builder(1000, jobService);
JobInfo jobInfo = builder
.setPeriodic(3*1000)
.build();
scheduler.schedule(jobInfo);
不同点
- 使用WorkerManager 的语义话程度比JobScheduler 语义程度高,WorkerManager有明确的区分一次性任务还是周期性任务,JobScheduler 只能通过约束条件设置,任务最大的延迟来,执行一次性任务。周期性任务与和非周期性任务是互斥的,在WorkerManager 中通过两个明确的类将两者进行区别,在JobScheduler 是通过设置约束条件时抛出异常来体现,因此WorkerManager 的易用性更强。
- 自定义Worker 中的doWork 是在线程中的,可以执行耗时操作。自定义的JobService 中的
onStartJob
,onStopJob
在UI线程,不能执行耗时任务。 - WorkerManager的时间约束条件
setInitialDelay
,含义是在满足约束条件的前提下,初始延迟时间,如果没有满足约束条件会一直等到约束条件满足,再次判断是否满足时间约束条件,如果满足约束条件则只需Worker。JobScheduler 的时间约束条件setOverrideDeadline
的含义是到了最大延迟时间后,即使不满足约束条件也会只需JobService。 - WorkerManager 中的自定义Worker返回值有着明确的含义,成功
Result.success()
;失败Result.failure()
;重试Result.retry()
;JobScheduler 中的JobService 需要通过jobFinished
来确定,是否需要重试,jobFinished
中第二个参数为true,需要重试,否则不需要重试。 - WorkerManager 和 JobScheduler的兼容性不同,WorkerManager兼容到Android14,但是JobScheduler 最低的版本要求是21。因此WorkerManager的兼容范围更广。
JobId
- 在JobScheduler 中构建JobInfo 需要指定JobId,并且这个JobId必须是系统唯一的。这一个要求想要达到是有很大的难度的,在应用中使用代码很难获取到其他应用创建的JobId,因此很难保证JobId的唯一性。同时如果JobId不唯一,在Android 7.0 的某些机型测试时发现会造成调度时间的混乱,这个可能会被其人利用,这时候出现的错误是很难发现和排查的。
- 在WorkerManager 中取消了JobId的概念,取而代之的是使用UUID,并且UUID是系统来生成,这个做的好处是可以最大程度的规避JobScheduler 遇到的问题。是一个可喜的变化。
WorkerManager 详细使用
下面记录WorkerManager的使用方式。
Worker状态
使用WorkerManager中最为重要的一步是继承Worker,其中Worker是具有状态,并且状态可以监控的,因此可以根据Worker的状态灵活对其进行控制。
Worker状态其实和线程的状态非常像,Worker状态如下:
- ENQUEUED 排队(就绪)状态,这时候自定义的Worker已进入队列,在等待约束条件满足。
- RUNNING 运行状态。
- SUCCEEDED 成功结束状态
- FAILED 失败结束状态
- BLOCKED 阻塞状态
- CANCELLED 取消状态
监听Worker状态源码,在必要的情况下面可以根据不同的状态获取数据,进行自定义的数据变化如下:
WorkManager.getInstance(getContext()).getWorkInfoByIdLiveData(firstWork.getId())
.observe(getViewLifecycleOwner(), new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
if (workInfo != null){
switch (workInfo.getState()){
case RUNNING:
Log.d(TAG,"运行状态");
break;
case SUCCEEDED:
Log.d(TAG,"成功");
break;
...
default:
break;
}
}
}
});
构建任务
构建一次性任务参数
构建一次性任务参数源码如下
OneTimeWorkRequest firstWork = new OneTimeWorkRequest.Builder(FirstWork.class)
.setInitialDelay(3000, TimeUnit.MILLISECONDS)// 设置任务初始延时
.setConstraints(new Constraints.Builder().build())// 设置约束条件
//.setInputMerger(new InputMerger()) //在工作链的时候使用
.setInputData(new Data.Builder().build()) // 设置输入数据
.setBackoffCriteria(BackoffPolicy.LINEAR, 11, TimeUnit.SECONDS)// 设置重试策略
.addTag("first_work") // 添加tag,方便使用
.keepResultsForAtLeast(10,TimeUnit.SECONDS) // Worker 运行结果保留时间
//.setInitialRunAttemptCount() 设置任务运行的次数(测试重试策略的时候使用),测试使用
//.setInitialState()// 设置Worker的状态,也是测试使用,应用不能调用
//.setScheduleRequestedAt() // android 做测试使用,应用是一般不调用
//.setPeriodStartTime(3000, TimeUnit.MILLISECONDS)// android 做测试使用,应用是一般不调用
.build();
构建周期性任务参数
构建周期性任务参数如下
PeriodicWorkRequest thirdWork = new PeriodicWorkRequest.Builder(SecondWork.class,
15, TimeUnit.MINUTES, 15, TimeUnit.MINUTES)
.setInputData(new Data.Builder().putInt("number",i).build())
//.setInitialDelay(10,TimeUnit.SECONDS)// 周期性任务的初始延迟时间,不设置,在满足条件后立即执行.
.setConstraints(new Constraints.Builder().build())// 设置约束条件
//.setInputMerger(new InputMerger()) //在工作链的时候使用
.setInputData(new Data.Builder().build()) // 设置输入数据
.setBackoffCriteria(BackoffPolicy.LINEAR, 11, TimeUnit.SECONDS)// 设置重试策略
.addTag("first_work") // 添加tag,方便使用
.keepResultsForAtLeast(10,TimeUnit.SECONDS) // Worker 运行结果保留时间
.build();
比较一次性任务和周期性任务约束参数
由此可见,构建一次性任务参数和周期性任务参数处理Builder传入的参数不同,其他的约束参数是相同的。这也跟 OneTimeWorkRequest
与PeriodicWorkRequest
都继承与WorkRequest
相关。相同的约束参数都是封装在WorkRequest
,特殊的参数封装在子类中。后续有自定义的任务可以继承WorkRequest
,实现自定义化。
构建约束条件
在JobScheduler的约束条件在构建JobInfo中设置,在WorkerManager中有单独的设置类Constraints
,可配置的约束条件如下
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.setRequiresBatteryNotLow(true)
//.addContentUriTrigger(new Uri(),false)
.build()
任务具有可管理性
WorkerManager 比 JobScheduler 更为友好的一方面是可以更好管理Worker。WokerManager通过以下接口,对Worker进行管理
public abstract @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id);
public abstract @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id);
public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(@NonNull String tag);
public abstract @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosByTag( @NonNull String tag);
...
观察任务的状态
WorkerManger支持对任务状态的监控,使用代码如下
WorkManager.getInstance(getContext()).getWorkInfoByIdLiveData(firstWork.getId())
.observe(getViewLifecycleOwner(), new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
if (workInfo != null) {
switch (workInfo.getState()) {
case RUNNING:
Log.d(TAG, "进度 = " + workInfo.getProgress().getInt("progress", 0));
break;
case SUCCEEDED:
Log.d(TAG, "成功");
break;
default:
break;
}
}
}
});
任务的参数
WorkerManager可以方便的在调用者,和Woker之间传递参数。示例如下
- Woker可以向WorkerManager传递运行过程中的参数和运行结果的参数。
public class FirstWork extends Worker{
public Result doWork() {
Log.d(TAG,"thread id = "+Thread.currentThread().getId());
Log.d(TAG,"执行时间 :"+ DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
String number = getInputData().getString("number");
// 向调用者(WorkManager)传递中间过程参数,
setProgressAsync(new Data.Builder().putInt("progress", 100).build());
int aa = Integer.parseInt(number);
// 构建返回值
Data outputData = new Data.Builder()
.putString("number",String.valueOf(aa))
.build();
// 向调用者(WorkManager)传递执行结果。
return Result.success(outputData);
}
}
- 在调用Woker时,可以传递运行时需要的参数,以及监听Woker回传的参数。示例代码如下
Data data = new Data.Builder()
.putString("number", "1")
.build();
OneTimeWorkRequest firstWork = new OneTimeWorkRequest.Builder(FirstWork.class)
.setInitialDelay(3000, TimeUnit.MILLISECONDS)
.setInputData(data)// 传入Worker运行时需要的参数。
.setBackoffCriteria(BackoffPolicy.LINEAR, 11, TimeUnit.SECONDS)
.addTag("first_work")
.build();
监听Worker回传的参数
WorkManager.getInstance(getContext()).getWorkInfoByIdLiveData(firstWork.getId())
.observe(getViewLifecycleOwner(), new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
if (workInfo != null) {
switch (workInfo.getState()) {
case RUNNING:
Log.d(TAG, "进度 = " + workInfo.getProgress().getInt("progress", 0));
break;
case SUCCEEDED:
Log.d(TAG, "成功");
break;
default:
break;
}
}
}
});
任务的取消
取消worker代码如下
WorkManager.getInstance(getContext()).cancelAllWork()
WorkManager.getInstance(getContext()).cancelWorkById()
WorkManager.getInstance(getContext()).cancelAllWorkByTag()