WorkerManager 适用于执行可以延迟(不需要精确时间)但是必须要稳定执行的的后台任务。适用于向后台同步应用数据,发送日志,应用检查更新等不需要及时完成的后台任务。在本文中这类型的后台任务,命名为延迟后台任务,方便理解。
在Android8.0以后Android系对后台的服务有了严格的限制,因此执行后台任务,需要通过系统调度的方式来执行,Google官方推荐使用JobScheduler 作业替换后台 Service。但是WorkerManager 是JetPack中的一个组成部分,并且完成能够胜任JobScheduler,并且有比JobScheduler 更加好用,因此推荐使用WorkerManager。文章除了介绍WorkerManager的用法,也会将WorkerManager 与 JobScheduler进行对比。
后台延迟任务可以分为一次性任务和周期性任务。一次性任务,并不是只执行一次,如果任务没有执行成功,可以进行重试策略,重试策略WorkerManager 和 JobScheduler 的限制条件是一样的,最小的重试时间为10S,最大的重试时间为5小时。
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();
}
}
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"));
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);
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();
}
}
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 的区别。
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);
onStartJob
,onStopJob
在UI线程,不能执行耗时任务。setInitialDelay
,含义是在满足约束条件的前提下,初始延迟时间,如果没有满足约束条件会一直等到约束条件满足,再次判断是否满足时间约束条件,如果满足约束条件则只需Worker。JobScheduler 的时间约束条件setOverrideDeadline
的含义是到了最大延迟时间后,即使不满足约束条件也会只需JobService。Result.success()
;失败Result.failure()
;重试Result.retry()
;JobScheduler 中的JobService 需要通过jobFinished
来确定,是否需要重试,jobFinished
中第二个参数为true,需要重试,否则不需要重试。下面记录WorkerManager的使用方式。
使用WorkerManager中最为重要的一步是继承Worker,其中Worker是具有状态,并且状态可以监控的,因此可以根据Worker的状态灵活对其进行控制。
Worker状态其实和线程的状态非常像,Worker状态如下:
监听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之间传递参数。示例如下
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);
}
}
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()