Android WorkerManager 的使用

简介

WorkerManager 适用于执行可以延迟(不需要精确时间)但是必须要稳定执行的的后台任务。适用于向后台同步应用数据,发送日志,应用检查更新等不需要及时完成的后台任务。在本文中这类型的后台任务,命名为延迟后台任务,方便理解。
在Android8.0以后Android系对后台的服务有了严格的限制,因此执行后台任务,需要通过系统调度的方式来执行,Google官方推荐使用JobScheduler 作业替换后台 Service。但是WorkerManager 是JetPack中的一个组成部分,并且完成能够胜任JobScheduler,并且有比JobScheduler 更加好用,因此推荐使用WorkerManager。文章除了介绍WorkerManager的用法,也会将WorkerManager 与 JobScheduler进行对比。

简要使用

后台延迟任务可以分为一次性任务和周期性任务。一次性任务,并不是只执行一次,如果任务没有执行成功,可以进行重试策略,重试策略WorkerManager 和 JobScheduler 的限制条件是一样的,最小的重试时间为10S,最大的重试时间为5小时。

一次性任务

WorkerManager 一次性任务

  1. 继承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();
    }
}
  1. 创建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 一次性任务

  1. 继承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;
    }
}
  1. 获取系统调度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 周期性任务

  1. 继承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();
    }
}
  1. 创建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 周期性任务

  1. 继承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;
    }
}
  1. 获取系统调度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);     

不同点

  1. 使用WorkerManager 的语义话程度比JobScheduler 语义程度高,WorkerManager有明确的区分一次性任务还是周期性任务,JobScheduler 只能通过约束条件设置,任务最大的延迟来,执行一次性任务。周期性任务与和非周期性任务是互斥的,在WorkerManager 中通过两个明确的类将两者进行区别,在JobScheduler 是通过设置约束条件时抛出异常来体现,因此WorkerManager 的易用性更强。
  2. 自定义Worker 中的doWork 是在线程中的,可以执行耗时操作。自定义的JobService 中的onStartJob,onStopJob在UI线程,不能执行耗时任务。
  3. WorkerManager的时间约束条件setInitialDelay,含义是在满足约束条件的前提下,初始延迟时间,如果没有满足约束条件会一直等到约束条件满足,再次判断是否满足时间约束条件,如果满足约束条件则只需Worker。JobScheduler 的时间约束条件setOverrideDeadline的含义是到了最大延迟时间后,即使不满足约束条件也会只需JobService。
  4. WorkerManager 中的自定义Worker返回值有着明确的含义,成功Result.success();失败Result.failure();重试Result.retry();JobScheduler 中的JobService 需要通过jobFinished来确定,是否需要重试,jobFinished中第二个参数为true,需要重试,否则不需要重试。
  5. WorkerManager 和 JobScheduler的兼容性不同,WorkerManager兼容到Android14,但是JobScheduler 最低的版本要求是21。因此WorkerManager的兼容范围更广。

JobId

  1. 在JobScheduler 中构建JobInfo 需要指定JobId,并且这个JobId必须是系统唯一的。这一个要求想要达到是有很大的难度的,在应用中使用代码很难获取到其他应用创建的JobId,因此很难保证JobId的唯一性。同时如果JobId不唯一,在Android 7.0 的某些机型测试时发现会造成调度时间的混乱,这个可能会被其人利用,这时候出现的错误是很难发现和排查的。
  2. 在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传入的参数不同,其他的约束参数是相同的。这也跟 OneTimeWorkRequestPeriodicWorkRequest都继承与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之间传递参数。示例如下

  1. 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);
    }
}
  1. 在调用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()

你可能感兴趣的:(android,android)