欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android架构组件WorkManager详解

程序员文章站 2022-06-10 14:38:37
...

       WorkManager架构组件是用来管理后台工作任务。这个时候你可能会奇怪了Android不是已经 有很多管理后台任务的类了么,比如JobScheduler, AlarmManger、在比如AsyncTask, ThreadPool。WorkManager。WorkManager的优势在哪里,我们为啥要使用WorkManager。我们从两个方面来说明WorkManager的优势

  • WorkManager对比JobScheduler, AlarmManger的优势:我们要知道虽然AlarmManager是一直存在但是JobScheduler是Android 5.x之后才有的。WorkManager的底层实现,会根据你的设备API的情况,自动选用JobScheduler, 或是AlarmManager来实现后台任务。
  • WorkManager对比AsyncTask, ThreadPool的优势:WorkManager里面的任务在应用退出之后还可以继续执行。AsyncTask, ThreadPool里面的任务在应用退出之后不会执行。

       WorkManager适用于那些在应用退出之后任务还需要继续执行的需求(比如应用数据上报服务器的情况),对应那些在应用退出的之后任务也需要终止的情况就需要选择ThreadPool、AsyncTask来实现。

一、WorkManager相关类介绍

       想使用WorkManager组件库,第一步咱们得先了解下WorkManager里面相关的几个类。

1.1、Worker

       Worker用于指定需要执行的具体任务。任务的具体逻辑在Worker里面写。
Worker是个抽象类。所以我们需要继承并实现这个类在定义自己的任务。

       Worker类里面几个比较关键的函数:任务逻辑实现函数,任务输入数据的获取函数,任务输出数据的设置函数。

    /**
     * 任务逻辑
     * @return 任务的执行情况,成功,失败,还是需要重新执行
     */
    @WorkerThread
    public abstract @NonNull Worker.Result doWork();

    /**
     * 任务的输入数据,有的时候可能需要我们传递参数进去,比如下载文件我们需要传递文件路基进去,
     * 在doWork()函数中通过getInputData()获取到我们传递进来的参数
     * @return Data参数
     */
    public final @NonNull Data getInputData() {
        return mExtras.getInputData();
    }

    /**
     * 设置我们任务输出结果
     * @param outputData 结果
     */
    public final void setOutputData(@NonNull Data outputData) {
        mOutputData = outputData;
    }

       doWork()函数的返回值:
- Worker.Result.SUCCESS:任务执行成功。
- Worker.Result.FAILURE:任务执行失败。
- Worker.Result.RETRY:任务需要重新执行,需要配合WorkRequest.Builder里面的setBackoffCriteria()函数使用。

1.2、WorkRequest

       WorkRequest代表一个单独的任务,是对Worker任务的包装,一个WorkRequest对应一个Worker类。我们可以通过WorkRequest来给Worker类添加约束细节,比如指定任务应该运行的环境,任务的输入参数,任务只有在有网的情况下执行等等。WorkRequest是一个抽象类,组件里面也给两个相应的子类:OneTimeWorkRequest(任务只执行一遍)、PeriodicWorkRequest(任务周期性的执行)。

  • WorkRequest.Builder: 创建WorkRequest对象的帮助类。
  • Constraints:指定任务运行的限制条件(例如,”仅当连接到网络时”)。使用Constraint.Builder来创建Constraints,并在创建WorkRequest之前把Constraints传给WorkRequest.Builder的setConstraints()函数。

WorkRequest里面常用函数介绍

    /**
     * 获取 WorkRequest对应的UUID
     */
    public @NonNull UUID getId();

    /**
     * 获取 WorkRequest对应的UUID string
     */
    public @NonNull String getStringId();

    /**
     * 获取WorkRequest对应的WorkSpec(包含了任务的一些详细信息)
     */
    public @NonNull WorkSpec getWorkSpec();

    /**
     * 获取 WorkRequest对应的tag
     */
    public @NonNull Set<String> getTags();

    public abstract static class Builder<B extends WorkRequest.Builder, W extends WorkRequest> {
        ...

        /**
         * 设置任务的退避/重试策略。比如我们在Worker类的doWork()函数返回Result.RETRY,让该任务又重新入队。
         */
        public @NonNull B setBackoffCriteria(
            @NonNull BackoffPolicy backoffPolicy,
            long backoffDelay,
            @NonNull TimeUnit timeUnit);


        /**
         * 设置任务的运行的限制条件,比如有网的时候执行任务,不是低电量的时候执行任务
         */
        public @NonNull B setConstraints(@NonNull Constraints constraints);

        /**
         * 设置任务的输入参数
         */
        public @NonNull B setInputData(@NonNull Data inputData);

        /**
         * 设置任务的tag
         */
        public @NonNull B addTag(@NonNull String tag);

        /**
         * 设置任务结果保存时间
         */
        public @NonNull B keepResultsForAtLeast(long duration, @NonNull TimeUnit timeUnit);
        @RequiresApi(26)
        public @NonNull B keepResultsForAtLeast(@NonNull Duration duration);
        ...
    }

       这里要稍微提下Builder的setBackoffCriteria()函数的使用场景,比较常用,一般当我们任务执行失败的时候任务需要重试的时候会用到这个函数,在任务执行失败的时候Worker类的doWork()函数返回Result.RETRY告诉这个任务要重试。那重试的策略就是通过setBackoffCriteria()函数来设置的。BackoffPolicy有两个值LINEAR(每次重试的时间线性增加,比如第一次10分钟,第二次就是20分钟)、EXPONENTIAL(每次重试时间指数增加)。

1.3、WorkManager

       管理任务请求和任务队列,我们需要把WorkRequest对象传给WorkManager以便将任务编入队列。通过WorkManager来调度任务,以分散系统资源的负载。

WorkManager常用函数介绍

    /**
     * 任务入队
     */
    public final void enqueue(@NonNull WorkRequest... workRequests);
    public abstract void enqueue(@NonNull List<? extends WorkRequest> workRequests);

    /**
     * 链式结构的时候使用,从哪些任务开始。
     * 比如我们有A,B,C三个任务,我们需要顺序执行。那我们就可以WorkManager.getInstance().beginWith(A).then(B).then(C).enqueue();
     */
    public final @NonNull WorkContinuation beginWith(@NonNull OneTimeWorkRequest...work);
    public abstract @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work);


    /**
     * 创建一个唯一的工作队列,唯一工作队列里面的任务不能重复添加
     */
    public final @NonNull WorkContinuation beginUniqueWork(
        @NonNull String uniqueWorkName,
        @NonNull ExistingWorkPolicy existingWorkPolicy,
        @NonNull OneTimeWorkRequest... work);
    public abstract @NonNull WorkContinuation beginUniqueWork(
        @NonNull String uniqueWorkName,
        @NonNull ExistingWorkPolicy existingWorkPolicy,
        @NonNull List<OneTimeWorkRequest> work);

    /**
     * 允许将一个PeriodicWorkRequest任务放到唯一的工作序列里面去,但是当队列里面有这个任务的时候你的提供替换的策略。
     */
    public abstract void enqueueUniquePeriodicWork(
        @NonNull String uniqueWorkName,
        @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
        @NonNull PeriodicWorkRequest periodicWork);

    /**
     * 通过UUID取消任务
     */
    public abstract void cancelWorkById(@NonNull UUID id);

    /**
     * 通过tag取消任务
     */
    public abstract void cancelAllWorkByTag(@NonNull String tag);

    /**
     * 取消唯一队列里面所有的任务(beginUniqueWork)
     */
    public abstract void cancelUniqueWork(@NonNull String uniqueWorkName);

    /**
     * 取消所有的任务
     */
    public abstract void cancelAllWork();

    /**
     * 获取任务的WorkStatus。一般会通过WorkStatus来获取返回值,LiveData是可以感知WorkStatus数据变化的
     */
    public abstract @NonNull LiveData<WorkStatus> getStatusById(@NonNull UUID id);
    public abstract @NonNull LiveData<List<WorkStatus>> getStatusesByTag(@NonNull String tag);

    /**
     * 获取唯一队列里面所有的任务(beginUniqueWork)的WorkStatus
     */
    public abstract @NonNull LiveData<List<WorkStatus>> getStatusesForUniqueWork(@NonNull String uniqueWorkName);

       beginWith(),beginUniqueWork()两个函数开启的队列的唯一区别在于,队列里面的任务能不能重复。beginWith()开始的队列里面的任务是可以重复的,beginUniqueWork()开始的队列里面的任务是不能重复的。

1.4、WorkStatus

       包含任务的信息。WorkManager为每个WorkRequest对象提供一个LiveData(WorkManager通过getStatusById、getStatusesByTag、getStatusesForUniqueWork函数来获取)。LiveData持有一个WorkStatus对象。LiveData是可以感知数据变化的。通过观察这个LiveData,我们可以确定任务的当前状态,并在任务完成后获得返回值。WorkStatus里面就包含的东西不多就任务的id、tag、状态、返回值。

通过如下方式来监听任务的状态

// 获取到LiveData然后监听数据变化
        WorkManager.getInstance().getStatusById(request.getId()).observe(this, new Observer<WorkStatus>() {
            @Override
            public void onChanged(@Nullable WorkStatus workStatus) {
                if (workStatus == null) {
                    return;
                }
                if (workStatus.getState() == State.ENQUEUED) {
                    mTextOut.setText("任务入队");
                }
                if (workStatus.getState() == State.RUNNING) {
                    mTextOut.setText("任务正在执行");
                }
                if (workStatus.getState().isFinished()) {
                    Data data = workStatus.getOutputData();
                    mTextOut.setText("任务完成" + "-结果:" + data.getString("key_name", "null"));
                }
            }
        });

1.5、Data

       Data是用于来给Worker设置输入参数和输出参数的。举个例子,比如我们需要去网络上下载图,那么需要给Worker传入下载地址(输入参数),在Worker执行成功之后我们又需要获取到图片在本地的保持路径(输出参数)。这这个传入传出都是通过Data来实现的。Data是一个轻量级的容器(不能超过10KB),Data通过key-value的形式来保存信息。

二、WorkManager使用

       前面讲了WorkManager里面常用的一些类,接下来就是WorkManager的使用了。

       我们把WorkManager的使用分为几个步骤:
- 继承Worker,处理任务的具体逻辑。
- OneTimeWorkRequest或者PeriodicWorkRequest包装Worker,设置Worker的一些约束添加,或者Worker的输入参数。
- 任务入队执行(如果是多个任务可以形成任务链在入队执行)。
- 监听任务的输出(LiveData的使用)。

2.1、任务的输入输出

       有些时候,一个任务的执行,我们可能需要从外部传入参数,在任务结束的时候需要把任务的结果告诉外部。比如我们去网络上下载一个图片,那咱们需要url地址(输入),在图片下载成功之后获取图片在本地的保存路径(输出)。

  • 输入参数:想要给任务传递输入参数需要在WorkRequest包装Worker的通过WorkRequest.Builder的setInputData()函数设置输入参数。之后任务在执行过程中可以通过getInputData()获取到传入的参数。
  • 输出参数:任务执行过程中如果想要结果传递给外界,需要在Worker中通过setOutputData()设置输出参数。之后如果想获取任务结果需要通过WorkManager.getInstance().getStatusById()或者WorkManager.getInstance().getStatusesByTag()先获取到LiveData。然后通过LiveData来感知任务数据的变化。

       我们通过一个简单的实力来看下任务有输入输出的情况应该怎么处理。

定义一个任务

public class InputOutputWorker extends Worker {

    @NonNull
    @Override
    public Result doWork() {

        try {
            //模拟耗时任务
            Thread.sleep(3000);
            Data inputData = getInputData();
            //获取到输入的参数,我们又把输入的参数给outputData
            Data outputData = new Data.Builder().putString("key_name", inputData.getString("key_name", "no data")).build();
            setOutputData(outputData);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return Result.SUCCESS;
    }
}

OneTimeWorkRequest设置任务的输入,执行任务,任务的执行过程中设置输出,LiveData感知任务结果

    private void startWorker() {
        // 定义一个OneTimeWorkRequest,并且关联InputOutputWorker。设置输入参数
        Data inputData = new Data.Builder().putString("key_name", "江西高安").build();
        OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(InputOutputWorker.class)
                                                           .setInputData(inputData)
                                                           .build();
        // 任务入队,WorkManager调度执行
        WorkManager.getInstance().enqueue(request);
        // 获取到LiveData然后监听数据变化
        WorkManager.getInstance().getStatusById(request.getId()).observe(this, new Observer<WorkStatus>() {
            @Override
            public void onChanged(@Nullable WorkStatus workStatus) {
                if (workStatus == null) {
                    return;
                }
                if (workStatus.getState() == State.ENQUEUED) {
                    mTextOut.setText("任务入队");
                }
                if (workStatus.getState() == State.RUNNING) {
                    mTextOut.setText("任务正在执行");
                }
                if (workStatus.getState().isFinished()) {
                    Data data = workStatus.getOutputData();
                    mTextOut.setText("任务完成" + "-结果:" + data.getString("key_name", "null"));
                }
            }
        });
    }

2.2、周期任务

       WorkManager组件库里面提供了一个专门做周期性任务的类PeriodicWorkRequest。但是PeriodicWorkRequest类有一个限制条件最小的周期时间是15分钟。

    private void startWorker() {
        // 定义一个PeriodicWorkRequest,并且关联PeriodicWorker。任务15m循环(源码里面已经规定了最小时间间隔15分钟)
        PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(PeriodicWorker.class, 15, TimeUnit.MINUTES).build();
        // 任务入队,WorkManager调度执行
        WorkManager.getInstance().enqueue(request);
    }

2.3、任务添加约束

       有些情况下,有些任务可能需要添加额外的约束,比如只能在联网的情况下才能执行,只有在设备空闲的情况下才能执行等等。WorkManager里面所有的约束条件都是通过Constraints来实现的,Constraints也是通过Constraints.Builder()来实现的。

       关于任务的约束要注意,可能我们任务加入的那个时刻候没有满足约束的条件,任务没有执行。但是过后一旦约束条件满足之后任务会自动执行的。

Constraints常用函数-可以添加的限制如下

    /**
     * 是否在充电状态下执行任务
     */
    public @NonNull Constraints.Builder setRequiresCharging(boolean requiresCharging);


    /**
     * 是否在设备空闲的时候执行
     */
    @RequiresApi(23)
    public @NonNull Constraints.Builder setRequiresDeviceIdle(boolean requiresDeviceIdle);


    /**
     * 指定网络状态执行任务
     * NetworkType.NOT_REQUIRED:对网络没有要求
     * NetworkType.CONNECTED:网络连接的时候执行
     * NetworkType.UNMETERED:不计费的网络比如WIFI下执行
     * NetworkType.NOT_ROAMING:非漫游网络状态
     * NetworkType.METERED:计费网络比如3G,4G下执行。
     */
    public @NonNull Constraints.Builder setRequiredNetworkType(@NonNull NetworkType networkType);


    /**
     * 在电量不足的是否是否可以执行任务
     */
    public @NonNull Constraints.Builder setRequiresBatteryNotLow(boolean requiresBatteryNotLow);


    /**
     * 在存储容量不足时是否可以执行
     */
    public @NonNull Constraints.Builder setRequiresStorageNotLow(boolean requiresStorageNotLow);

    /**
     * 当Uri有更新的时候是否执行任务
     */
    @RequiresApi(24)
    public @NonNull Constraints.Builder addContentUriTrigger(Uri uri, boolean triggerForDescendants);

       我们举一个简单的例子,比如我们限制任务只有在wifi的状态下才能执行。

    /**
     * 启动约束任务
     */
    private void startWorker() {
        // 设置只有在wifi状态下才能执行
        Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).build();
        // 设置约束条件
        OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(ConstraintsWorker.class).setConstraints(constraints).build();
        // 任务入队,WorkManager调度执行
        WorkManager.getInstance().enqueue(request);
    }

2.3、任务取消

       每个任务都有自己独特的UUID,我们可以通过任务的UUID找到任务,然后取消他。除了UUID的方式,我们还可以给任务添加tag,然后通过tag来取消任务(可以给多个任务添加同一个tag,同时取消)。

       我们简单的实现一个通过tag取消任务的例子

    /**
     * 给任务设置tag
     */
    private void startWorker() {
        // 给任务设置tag->cancel
        OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(ConstraintsWorker.class).addTag("cancel").build();
        // 任务入队,WorkManager调度执行
        WorkManager.getInstance().enqueue(request);
    }

    /**
     * 通过tag取消任务
     */
    private void cancelWorker() {
        WorkManager.getInstance().cancelAllWorkByTag("cancel");
    }

2.4、链式任务

       有时候我们想让某些按照特定的顺序行来执行。WorkManager允许我们创建和排队多个任务的工作序列,以及它们应该以什么顺序运行。这个就是链式任务了。

       任务链里面的任何一个任务返回WorkerResult.FAILURE,则整个任务链终止。

       链式任务的关键在WorkContinuation,通过WorkContinuation来整理好队列(是顺序执行,还是组合执行)然后入队执行。

WorkContinuation里面常用函数介绍


    /**
     * 顺序执行任务,
     * 如果then的参数指定了多个任务那么这些任务的执行顺序是没有规律的,但是一定要等这个then函数参数里面所有任务都执行完了才会去执行下一个then的任务
     * 任务任务返回Worker.WorkerResult.FAILURE整个则整个任务链结束
     */
    public final @NonNull WorkContinuation then(@NonNull OneTimeWorkRequest... work);
    public abstract @NonNull WorkContinuation then(@NonNull List<OneTimeWorkRequest> work);


    /**
     * 这些都是static函数哦,用于组合任务
     */
    public static @NonNull WorkContinuation combine(@NonNull WorkContinuation... continuations);
    public static @NonNull WorkContinuation combine(@NonNull List<WorkContinuation> continuations);
    public static @NonNull WorkContinuation combine(@NonNull OneTimeWorkRequest work,
        @NonNull WorkContinuation... continuations);
    public static @NonNull WorkContinuation combine(@NonNull OneTimeWorkRequest work,
        @NonNull List<WorkContinuation> continuations);

    /**
     * 获取任务链中所有任务的LiveData,用于监听任务链里面任务的结果
     */
    public abstract @NonNull LiveData<List<WorkStatus>> getStatuses();

    /**
     * 任务链中的任务入队,开始执行。
     */
    public abstract void enqueue();

2.4.1、任务顺序执行

       任务顺序执行,WorkContinuation的then()函数的使用。假设我们有A,B,C三个任务需要按顺序执行。

    /**
     * A,B,C三个任务顺序执行
     */
    private void startWorker() {
        // A
        OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(OrderWorkerA.class).build();
        // B
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(OrderWorkerB.class).build();
        // C
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(OrderWorkerC.class).build();
        // 任务入队,WorkManager调度执行
        WorkManager.getInstance().beginWith(requestA).then(requestB).then(requestC).enqueue();
    }

       为了把顺序任务说的更加彻底,我们在来一个例子。

WorkManager.getInstance()
    // First, run all the A tasks (in parallel):
    .beginWith(workA1, workA2, workA3)
    // ...when all A tasks are finished, run the single B task:
    .then(workB1, workB2)
    // ...then run the C tasks (in any order):
    .then(workC1, workC2)
    .enqueue();

       上诉代码中beginWith函数里面的workA1, workA2, workA3三个任务是平行(同时)执行的,而且要等workA1, workA2, workA3都执行完才能做下一步the里的任务。then(workB1, workB2)里面的workB1,workB2的执行顺序是没有规律的,但是一定要等到workB1,workB2都执行玩才能执行下一步的then里面的任务。workC1, workC2的执行顺序也是没有规律的。

2.4.2、组合任务

       想要组合任务,就需要用到WorkContinuation的combine()函数了。我们用一个非常见到的任务来说明组合任务的执行。比如我们想要实现如下图所示的链试效果。

Android架构组件WorkManager详解

上图对应代码如下

    /**
     * 组合任务
     */
    private void startWorker() {
        OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(ConbineWorkerA.class).build();
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(ConbineWorkerB.class).build();
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(ConbineWorkerC.class).build();
        OneTimeWorkRequest requestD = new OneTimeWorkRequest.Builder(ConbineWorkerD.class).build();
        OneTimeWorkRequest requestE = new OneTimeWorkRequest.Builder(ConbineWorkerE.class).build();
        //A,B任务链
        WorkContinuation continuationAB = WorkManager.getInstance().beginWith(requestA).then(requestB);
        //C,D任务链
        WorkContinuation continuationCD = WorkManager.getInstance().beginWith(requestC).then(requestD);
        //合并上面两个任务链,在接入requestE任务,入队执行
        WorkContinuation.combine(continuationAB, continuationCD).then(requestE).enqueue();
    }

2.4.3、任务链中任务数据流(每个任务的输入输出)

       在任务链中,我们可能会有这样的需求,任务之间的数据是相互依赖的,下一个任务需要上一个任务的输出数据。这种情况我们就称之为任务链中任务的数据流。其实强大的WorkManager已经帮我们设计好了。WorkManager会把上一个任务的输出自动作为下一个人任务的输入。

2.4.3.1、顺序任务的数据流

       因为WorkManager设计的时候已经帮我们设计好了上一任务的输出会自动作为下一个任务的输入。所以顺序任务的数据流是非常好处理的。上一个任务调用setOutputData()返回其结果,下一个任务调用getInputData()来获取上一个任务的结果。我们用一个简单的实例来说明。A,B,C三个顺序任务。A任务输出10,B任务得到A任务的值再乘以10,最后把结果给到C任务。我们来看下这种情况下的代码应该怎么写。

A任务

/**
 * A任务输出10
 */
public class StreamThenWorkerA extends Worker {

    @NonNull
    @Override
    public Result doWork() {
        Data data = new Data.Builder().putInt("a_out", 10).build();
        setOutputData(data);
        return Result.SUCCESS;
    }
}

B任务

/**
 * 得到A任务的输出在乘以10,做为输出
 */
public class StreamThenWorkerB extends Worker {

    @NonNull
    @Override
    public Result doWork() {
        //先得到A任务的输出值
        Data inputData = getInputData();
        int a_out = inputData.getInt("a_out", 0);
        //把A任务的输出×10在给到C任务
        Data data = new Data.Builder().putInt("b_out", a_out * 10).build();
        setOutputData(data);
        return Result.SUCCESS;
    }
}

C任务

/**
 * 只是做一个简单的打印
 */
public class StreamThenWorkerC extends Worker{

    @NonNull
    @Override
    public Result doWork() {
        Data inputData = getInputData();
        int b_out = inputData.getInt("b_out", 0);
        //获取到B任务的输出,我们只是做一个简单的输出。
        Log.d("tuacy", "value = " + b_out);
        return Result.SUCCESS;
    }
}

执行任务

    /**
     * 顺序任务的数据流
     * A,B,C三个任务。A,输出10,B任务得到A任务的值×10,最后给到C任务。
     */
    private void startThenWorker() {
        OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(StreamThenWorkerA.class).build();
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(StreamThenWorkerB.class).build();
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(StreamThenWorkerC.class).build();
        WorkManager.getInstance().beginWith(requestA).then(requestB).then(requestC).enqueue();
    }

2.4.3.2、组合任务的数据流

       组合任务的数据流稍稍复杂一点,因为涉及到多个任务的输出同时作为一个任务的输入,这个时候多个任务的输出需要合并一个输入。这个时候就会有合并规则一说了(不同的任务中有相同的key应该怎么处理),WorkManager通过OneTimeWorkRequest.Builder类的setInputMerger()函数来指定多个任务输入流的合并规则。参数是继承自InputMerger的类。InputMerger是一个抽象类,WorkManager也给我们提供了两种合并规则:ArrayCreatingInputMerger、OverwritingInputMerger。

  • ArrayCreatingInputMerger:所有key对应的value都会放到数组里面,有相同的key的话,数组慢慢扩大。比如有A、B两个任务的输出需要组合到一起。A任务输出里面有一个key:a_key->100。B任务里面有两个key(有个key和A任务是相同的):b_key->100、a_key->200。最后通过ArrayCreatingInputMerger规则组合的结果是:a_key对应一个数组,数组里面有两个元素100和200、b_key也对应一个数组,里面只有一个元素100。这个时候在下一个任务中想要获取合并之后的输入必须使用getIntArray(),因为现在key对应的value是一个数组了。
  • OverwritingInputMerger:如果有相同的key,直接覆盖。我通过测试发现OverwritingInputMerger没效果,表现形式和ArrayCreatingInputMerger一样

       我们还是用一个简单的例子来说明组合任务的数据流,我们有A,B,C三个任务。A,B任务合并再执行C任务。在C任务中获取A,B两个任务的输出。

A任务的输出中只有一个key: a_key -> 100

/**
 * A任务的输出中只有一个key: a_key -> 100
 */
public class StreamCombineWorkerA extends Worker {

    @NonNull
    @Override
    public Result doWork() {
        Data data = new Data.Builder().putInt("a_key", 100).build();
        setOutputData(data);
        return Result.SUCCESS;
    }
}

B任务的输出中有两个key:b_key -> 100、a_key -> 200,有个key在A任务中也出现了

/**
 * B任务的输出中有两个key:b_key -> 100、a_key -> 200
 * 有个key在A任务中也出现了
 */
public class StreamCombineWorkerB extends Worker {

    @NonNull
    @Override
    public Result doWork() {
        Data data = new Data.Builder().putInt("b_key", 100).putInt("a_key", 200).build();
        setOutputData(data);
        return Result.SUCCESS;
    }
}

C任务只是简单的获取A,B任务的输出

/**
 * 在C任务中获取到A,B任务的输出。
 *
 */
public class StreamCombineWorkerC extends Worker {

    @NonNull
    @Override
    public Result doWork() {
        Data data = getInputData();

        // 注意;这里我用的是getIntArray
        int[] aKeyValueList = data.getIntArray("a_key");
        int[] bKeyValueList = data.getIntArray("b_key");
        Log.d("tuacy", "a_key = " + aKeyValueList[0]);
        Log.d("tuacy", "b_key = " + bKeyValueList[0]);

        return Result.SUCCESS;
    }
}

启动组合任务,调用setInputMerger(
OverwritingInputMerger.class)来设置合并规则

    private void startCombineWorker() {
        OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(StreamCombineWorkerA.class).build();
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(StreamCombineWorkerB.class).build();
        // 设置合并规则OverwritingInputMerger
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(StreamCombineWorkerC.class).setInputMerger(
            OverwritingInputMerger.class).build();
        //A任务链
        WorkContinuation continuationA = WorkManager.getInstance().beginWith(requestA);
        //B任务链
        WorkContinuation continuationB = WorkManager.getInstance().beginWith(requestB);
        //合并上面两个任务链,在接入requestE任务,入队执行
        WorkContinuation continuation = WorkContinuation.combine(continuationA, continuationB).then(requestC);
        continuation.enqueue();
    }

2.4.4、唯一工作队列

       我们上面例子中所有的链试任务的队列都是通过WorkManager.getInstance().beginWith()来创建的。这种方式创建的链试任务没啥限制条件,任务随便怎么入队。要是某些场景我们需要同一个任务不能重复入队怎么办。这个时候就需要唯一工作队列了。

       WorkManager允许我们创建一个唯一的工作队列。唯一工作队列指的是这个队列中任务不能重复入队。WorkManager中通过beginUniqueWork()来建一个唯一队列。每个唯一工作队列创建的时候都必须指定一个队列名字,同时还得指定ExistingWorkPolicy当WorkManager里面已经有一个相同的唯一队列时候的处理方式。ExistingWorkPolicy有三个值:REPLACE(取消现有的序列并将其替换为新序列)、KEEP(保持现有顺序并忽略新请求)、APPEND(将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务)。

       如果在唯一工作队列中多次加入同一个任务,程序会异常退出。

    /**
     * A,B,C三个任务加入到唯一工作队列中去
     */
    private void startWorker() {
        // A
        OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(OrderWorkerA.class).build();
        // B
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(OrderWorkerB.class).build();
        // C
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(OrderWorkerC.class).build();
        // 任务入队,WorkManager调度执行
        WorkManager.getInstance().beginUniqueWork("unique", ExistingWorkPolicy.KEEP, requestA)
                   .then(requestB)
                   .then(requestC)
                   .enqueue();
    }

       关于WorkManager的任务就讲这么写,如果大家在使用过程中有什么疑问,欢迎留言。最后给出本文涉及到的实例下载地址https://github.com/tuacy/WorkManagerDev