WorkManager彻底解析+重构轮询系统

花两个周末写完的,创做不容易啊,转载请标明出处哈:html

csdn:blog.csdn.net/u013309870/… 掘金:juejin.im/post/5c4472…java

前言

以前用IntentService写了一个轮询框架,可是并非很好,后面一直想找个其余方式来改写一下,找了好多资料发现了WorkManager,WorkManager是google提供的一个很是优秀的后台任务管理框架,对于提交给WorkManager的任务能够当即执行也能够在适当的时候执行,能够执行一次也能够根据条件循环执行屡次,而且对于多个任务WorkManager能够管理任务的执行路径前后执行顺序等。本文先介绍WorkManager的用法,而后使用WorkManager来重构以前的轮询框架。android

什么是Workmanager

WorkManager是google提供的异步执行任务的管理框架,会根据手机的API版本和应用程序的状态来选择适当的方式执行任务。当应用在运行的时候会在应用的进程中开一条线程来执行任务,当退出应用时,WorkManager会选择根据设备的API版本使用适合的算法调用JobScheduler或者Firebase JobDispatcher,或者AlarmManager来执行任务。以下图: 算法

在这里插入图片描述
由上图能够看出WorkManager管理任务执行时底层仍是调用了JobScheduler,JobDispatcher,AlarmManager,不过WorkManager会根据Android系统的API和应用的运行状态来选择合适的方式执行,并不用咱们本身去考虑应用复杂的运行状态来进行选择使用JobScheduler仍是JobDispatcher或者AlarmManager调用规则以下图:
在这里插入图片描述

WorkManager在项目中配置

使用WorkManager须要gradle依赖,进行一下简单配置就可使用了。找到项目中的app/build.gradle目录下在上面加上下面的依赖。app

dependencies {
    // 其余依赖配置
    def work_version = "1.0.0-beta02"
    implementation "android.arch.work:work-runtime:$work_version"
}
复制代码

能够在developer.android.com/topic/libra…获取当前的work-runtime版本而且设置正确的版本号。框架

WorkManager主要类及使用

以下图给出了WorkManager中主要的类以及关系图,黄色区域是最主要的三个类,构成了WorkManager的基本框架,红色部分和绿色部分是关联的黄色部分的具体实现或者类里面包含一些规则或数据。 异步

在这里插入图片描述
一、Worker处理要执行的任务的具体逻辑。

二、WorkerRequest表明一个独立的能够执行的任务,以及任务执行时的条件和规则,好比说任务执行一次仍是屡次以及任务的触发条件是什么任务有什么约束等。ide

三、WorkManager提供队列将要执行的WorkerRequest放到队列中管理和执行。 以下图,三个主要类的关系: oop

在这里插入图片描述
下面分别介绍三个类的做用和使用方法。

Worker

Worker是一个抽象类,当有一个要执行的任务的时候能够继承Worker类,重写doWork()方法在doWork()方法中实现具体任务的逻辑。post

public class MyWorker extends Worker {
    public MyWorker( @NonNull Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
    }
    @NonNull
    @Override
    public Worker.Result doWork() {

        Context applicationContext = getApplicationContext();

        try {

            Bitmap picture = BitmapFactory.decodeResource(
                    applicationContext.getResources(),
                    R.drawable.test);
                    
            return Worker.Result.SUCCESS;
            
        } catch (Throwable throwable) {
        
            return Worker.Result.FAILURE;
            
        }
    }
}
复制代码

在上面的MyWorker实例中,继承了Worker 而且重写了doWork()方法,须要注意的是doWork()方法是有返回值Worker.Result的,能够在任务执行成功是返回Worker.Result.SUCCESS,在任务执行出现异常时返回Worker.Result.FAILURE doWork()方法的返回值主要有三种 一、Worker.Result.SUCCESS 表示任务执行成功

二、Worker.Result.FAILURE 表示任务执行失败

三、Worker.Result.RETRY 通知WorkManager以后再尝试执行该任务

WorkRequest

WorkRequest要指定执行任务的Worker,也能够给WorkRequest加一些规则,好比说何时执行任务,任务执行一次仍是屡次,每个WorkRequest都有一个自动产生的惟一ID,能够根据惟一ID获取对应任务的状态以及是否取消对应的任务。以下图WorkRequest有两个实现类以下图:

在这里插入图片描述
一、OneTimeWorkRequest 任务只执行一次

OneTimeWorkRequest myWorkRequest =
        new OneTimeWorkRequest.Builder(MyWorker.class)
    .build();
    //将上面定义的MyWorker加入到OneTimeRequest.Builder方法中
WorkManager.getInstance().enqueue(myWorkRequest);//获取WorkManager实例并将WorkRequest进队
复制代码

二、PeriodicWorkRequest PeriodicWorkRequest重复执行任务,直到被取消才中止。首次执行是任务提交后当即执行或者知足所给的 Constraints条件。之后执行都会根据所给的时间间隔来执行。注意任务的执行可能会有延时,由于WorkManager会根据OS的电量进行优化。 假如设置的Periodic Work是24小时执行一次,有可能根据电池优化策略执行的过程以下:

1     | Jan 01, 06:00 AM
     2     | Jan 02, 06:24 AM
     3     | Jan 03, 07:15 AM
     4     | Jan 04, 08:00 AM
     5     | Jan 05, 08:00 AM
     6     | Jan 06, 08:02 AM
复制代码

由上面的执行时间能够看出,PeriodicWorkRequest并非准确的按照24小时来执行,会有必定的时间延迟。所以若是须要准确的间隔时间来执行任务的话不能使用PeriodicWorkRequest。

Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
PeriodicWorkRequest build = new PeriodicWorkRequest.Builder(MyWorker.class, 25, TimeUnit.MILLISECONDS)
           .addTag(TAG)
           .setConstraints(constraints)
           .build();

WorkManager instance = WorkManager.getInstance();
if (instance != null) {
          instance.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, build);
}
复制代码

Constraints

能够给任务加一些运行的Constraints条件,好比说当设备空闲时或者正在充电或者链接WiFi时执行任务。

Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // Many other constraints are available, see the
    // Constraints.Builder reference
     .build();
OneTimeWorkRequest myWork =
                new OneTimeWorkRequest.Builder(CompressWorker.class)
     .setConstraints(myConstraints)
     .build();
复制代码

WorkManager

WorkManager管理WorkRequest队列。并根据设备和其余条件选择执行的具体方式。在大部分状况在若是没有给队列加Contraints,WorkManager会当即执行任务。

WorkManager.getInstance().enqueue(myWork);
复制代码

若是要检查任务的执行状态能够经过获取WorkInfo,WorkInfo在WorkManager里面的LiveData<WorkInfo>中。下面是判断任务是否结束的方式。

WorkManager.getInstance().getWorkInfoByIdLiveData(myWork.getId())
    .observe(lifecycleOwner, workInfo -> {
        // Do something with the status
        if (workInfo != null && workInfo.getState().isFinished()) {
            // ...
        }
    });
复制代码

取消任务执行

经过任务的ID能够获取任务从而取消任务。任务ID能够从WorkRequest中获取。

UUID compressionWorkId = compressionWork.getId();
WorkManager.getInstance().cancelWorkById(compressionWorkId);
复制代码

注意并非全部的任务均可以取消,当任务正在执行时是不能取消的,固然任务执行完成了,取消也是意义的,也就是说当任务加入到ManagerWork的队列中可是尚未执行时才能够取消。

WorkManager多任务调度

有时候可能有不少任务须要执行,而且这些任务以前可能有前后顺序或者某些依赖关系,WorkManager提供了很好的方式。 一、前后顺序执行单个任务 好比说有三个任务workA,workB,workC,而且执行顺序只能时workA---->workB---->workC能够用以下的方式处理。

WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)  instance
    .then(workC)
    .enqueue();
复制代码

上面的workA,workB,workC,都是WorkRequest的子类实现对象。WorkManager会根据上面的前后顺序来执行workA,workB,workC,,可是若是执行过程当中三个任务中有一个失败,整个执行都会结束。而且返回Result.failure()二、前后顺序执行多个任务列 有时候可能要先执行一组任务,而后再执行下一组任务,可使用下面的方式来完成。

WorkManager.getInstance()
    // First, run all the A tasks (in parallel):
    .beginWith(Arrays.asList(workA1, workA2, workA3))
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in any order):
    .then(Arrays.asList(workC1, workC2))
    .enqueue();
复制代码

三、多路径前后执行 上面两种方式都是单路径执行,能够实现更加复杂的多路径执行方式,使用WorkContinuation.combine(List<OneTimeWorkRequest>)以下图要实现的执行方式:

在这里插入图片描述

WorkContinuation chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE);
chain3.enqueue();
复制代码

使用WorkManager遇到的问题

一、使用PeriodicWorkRequest只执行一次,并不重复执行。

WorkManager instance= new PeriodicWorkRequest.Builder(PollingWorker.class, 10, TimeUnit.MINUTES)
                .build();
复制代码

缘由:PeriodicWorkRequest默认的时间间隔是15分钟若是设置的时间小于15分钟,就会出现问题。

解决方法:设置的默认时间必须大于或等于15分钟。另外要注意,就算设置的时间为15分钟也不必定间隔15分钟就执行。因此要精确的间隔时间执行,通常不用WorkManager,可使用AlarmManager来实现。

二、在doWork()方法中更新UI致使崩溃。 缘由:doWork()方法是在WorkManager管理的后台线程中执行的,更新UI操做只能在主线程中进行。

解决方法:当doWork()耗时方法执行完以后,将更新UI操做抛到主线中执行,能够用handle来实现,以下:

/** * Created time 15:32. * * @author huhanjun * @since 2019/1/23 */
public class PollingWorker extends Worker {
    public static final String TAG = "PollingWorker";

    @NonNull
    @Override
    public Result doWork() {
        Log.d(TAG, "doWork");
        try {
            polling();
            runOnUIThread(new Runnable() {
                @Override
                public void run() {
                    //更新UI操做
                }
            });
            return Result.SUCCESS;
        } catch (Exception e) {
            Log.d(TAG, "failure");
            return Result.FAILURE;
        }
    }

    private void polling() {
        Log.d(TAG, "Polling");
    }
//抛到主线程中执行
    private void runOnUIThread(Runnable runnable) {
        new Handler(Looper.getMainLooper()).post(runnable);
    }
}
复制代码

在执行完Polling方法后,将更新操做抛给了runOnUIThread()方法,这样就能够在上面代码注释的地方执行更新操做。 未完待续........下周末来重构以前的轮询框架:juejin.im/post/5c2e0e…

参考文献

一、developer.android.com/topic/libra…

二、codelabs.developers.google.com/codelabs/an…

个人csdn地址

blog.csdn.net/u013309870

相关文章
相关标签/搜索