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

Android架构组件-WorkManager

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

WorkManager API可以轻松指定可延迟的异步任务以及何时运行它们。 这些API允许您创建任务并将其交给WorkManager立即运行或在适当的时间运行。 例如,应用可能需要不时从网络下载新资源。 使用这些类,您可以设置任务,为其运行选择适当的环境(例如“仅在设备正在充电和在线”时),并将其交给WorkManager以在满足条件时运行。 即使您的应用程序强制退出或设备重新启动,该任务仍可保证运行

注意:WorkManager适用于需要保证系统即使应用程序退出也会运行它们的任务,例如将应用程序数据上传到服务器。如果应用程序进程消失,它不适用于可以安全终止的进程内后台工作;对于这种情况,我们建议使用ThreadPools。
WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行任务。如果WorkManager在应用程序运行时执行您的任务之一,WorkManager可以在您应用程序进程的新线程中运行您的任务。如果您的应用程序未运行,WorkManager会选择适当的方式来安排后台任务 - 根据设备API级别和包含的依赖项,WorkManager可能会使用JobScheduler,Firebase JobDispatcher或AlarmManager。您无需编写设备逻辑来确定设备具有哪些功能并选择适当的API;相反,您可以将您的任务交给WorkManager,让它选择最佳选项。

此外,WorkManager还提供了一些高级功能。例如,您可以设置一系列任务;当一个任务完成时,WorkManager将链中的下一个任务排队。您还可以通过观察其LiveData来检查任务的状态及其返回值。如果要显示指示任务状态的UI,这可能很有用。

本概述介绍了最重要的WorkManager功能。但是,还有更多功能可用;有关完整的详细信息,请参阅WorkManager参考文档。

注意:要将WorkManager库导入Android项目,请参阅向项目添加组件。

类和概念
WorkManager API使用几个不同的类。在某些情况下,您需要子类化其中一个API类。

最重要的课程是:

Worker:指定您需要执行的任务。 WorkManager API包含一个抽象的Worker类。您可以扩展此类并在此处执行工作。

WorkRequest:代表一项单独的任务。 WorkRequest对象至少指定应该执行任务的Worker类。但是,您还可以向WorkRequest对象添加详细信息,指定诸如运行任务的环境之类的内容。每个WorkRequest都有一个自动生成的唯一ID;您可以使用ID执行取消排队任务或获取任务状态等操作。 WorkRequest是一个抽象类;在您的代码中,您将使用直接子类之一,OneTimeWorkRequest或PeriodicWorkRequest。

WorkRequest.Builder:用于创建WorkRequest对象的辅助类。同样,您将使用其中一个子类OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder。

约束:指定任务运行时间的限制(例如,“仅在连接到网络时”)。使用Constraints.Builder创建Constraints对象,并在创建WorkRequest之前将Constraints传递给WorkRequest.Builder。

WorkManager:对工作请求进行排队和管理。您将WorkRequest对象传递给WorkManager以将任务排入队列。 WorkManager以这样一种方式安排任务,即分散系统资源的负载,同时遵守您指定的约束。

WorkStatus:包含有关特定任务的信息。 WorkManager为每个WorkRequest对象提供LiveData。 LiveData包含WorkStatus对象;通过观察LiveData,您可以确定任务的当前状态,并在任务完成后获取任何返回的值。

典型工作流程

假设您正在编写照片库应用程序,该应用程序需要定期压缩其存储的图像。 您希望使用WorkManager API来计划图像压缩。 在这种情况下,您在压缩发生时并不特别在意; 你想设置任务而忘记它。

首先,您将定义您的Worker类,并覆盖其doWork()方法。 您的worker类指定了如何执行操作,但没有任何关于何时应该运行任务的信息。

public class CompressWorker extends Worker {
    @Override
    public Worker.Result doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

接下来,基于该Worker创建OneTimeWorkRequest对象,然后使用WorkManager将任务排入队列:

OneTimeWorkRequest compressionWork =
       new OneTimeWorkRequest.Builder(CompressWorker.class).build();
WorkManager.getInstance().enqueue(compressionWork);

WorkManager选择适当的时间来运行任务,平衡诸如系统负载,设备是否插入等考虑因素。在大多数情况下,如果您未指定任何约束,WorkManager会立即运行您的任务。如果需要检查任务状态,可以通过获取相应的LiveData 的句柄来获取WorkStatus对象。例如,如果要检查任务是否已完成,可以使用以下代码:

WorkManager.getInstance().getStatusById(compressionWork.getId())
    .observe(lifecycleOwner, workStatus -> {
        // Do something with the status
        if (workStatus != null && workStatus.getState().isFinished()) {
            // ...
        }
    });

有关使用LiveData的详细信息,请参阅LiveData概述

任务约束

如果您愿意,可以指定任务运行时间的约束。例如,您可能希望指定该任务仅在设备空闲并连接到电源时运行。在这种情况下,您需要创建OneTimeWorkRequest.Builder对象,并使用该构建器创建实际的OneTimeWorkRequest:

// Create a Constraints that defines when the task should run
Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // Many other constraints are available, see the
    // Constraints.Builder reference
     .build();

// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest compressionWork =
                new OneTimeWorkRequest.Builder(CompressWorker.class)
     .setConstraints(myConstraints)
     .build();

然后像以前一样将新的OneTimeWorkRequest对象传递给WorkManager.enqueue()。在找到运行任务的时间时,WorkManager会考虑您的约束。

canceling 任务

您可以在排队后取消任务。要取消该任务,您需要其工作ID,您可以从WorkRequest对象获取该工作ID。例如,以下代码取消了上一节中的compressionWork请求:

UUID compressionWorkId = compressionWork.getId();
WorkManager.getInstance().cancelWorkById(compressionWorkId);

WorkManager尽最大努力取消任务,但这本质上是不确定的 - 当您尝试取消任务时,任务可能已经在运行或已完成。 WorkManager还提供了一种方法,可以在尽力而为的基础上取消唯一工作序列中的所有任务,也可以取消具有指定标记的所有任务。

高级功能

WorkManager API的核心功能使您可以创建简单,即发即弃的任务。除此之外,API还提供了先进的功能,可让您设置更精细的请求。

重复的任务

您可能需要重复执行一项任务。例如,照片管理器应用程序不希望仅压缩其照片一次。更有可能的是,它会经常检查其共享照片,并查看是否有任何新的或已更改的图像需要压缩。此重复任务可以压缩它找到的图像,或者,它可以触发新的“压缩此图像”任务。

要创建定期任务,请使用PeriodicWorkRequest.Builder类创建PeriodicWorkRequest对象,然后以与OneTimeWorkRequest对象相同的方式将PeriodicWorkRequest入队。例如,假设我们定义了一个PhotoCheckWorker类来识别需要压缩的图像。如果我们想每12小时运行一次库存任务,我们将创建一个PeriodicWorkRequest对象,如下所示:

new PeriodicWorkRequest.Builder photoCheckBuilder =
        new PeriodicWorkRequest.Builder(PhotoCheckWorker.class, 12,
                                        TimeUnit.HOURS);
// ...if you want, you can apply constraints to the builder here...

// Create the actual work object:
PeriodicWorkRequest photoCheckWork = photoCheckBuilder.build();
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(photoCheckWork);

WorkManager会尝试按您请求的时间间隔运行您的任务,具体取决于您施加的限制及其他要求。

链式任务

您的应用可能需要按特定顺序运行多个任务。 WorkManager允许您创建和排队指定多个任务的工作序列,以及它们应运行的顺序。

例如,假设您的应用有三个OneTimeWorkRequest对象:workA,workB和workC。 任务必须按该顺序运行。 要将它们排入队列,请使用WorkManager.beginWith()方法创建一个序列,并传递第一个OneTimeWorkRequest对象; 该方法返回一个WorkContinuation对象,该对象定义了一系列任务。 然后依次使用WorkContinuation.then()添加剩余的OneTimeWorkRequest对象,最后使用WorkContinuation.enqueue()将整个序列排入队列:

WorkManager.getInstance()
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
.enqueue();

WorkManager根据每个任务指定的约束以请求的顺序运行任务。 如果任何任务返回Worker.Result.FAILURE,则整个序列结束。

您还可以将多个OneTimeWorkRequest对象传递给任何beginWith()和.then()调用。 如果将多个OneTimeWorkRequest对象传递给单个方法调用,则WorkManager会在运行其余序列之前运行所有这些任务(并行)。 例如:

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(workB)
    // ...then run the C tasks (in any order):
    .then(workC1, workC2)
.enqueue();

您可以通过使用WorkContinuation.combine()方法连接多个链来创建更复杂的序列。例如,假设您要运行如下序列:
Android架构组件-WorkManager

图1.您可以使用WorkContinuation来设置复杂的链式任务。
要设置此序列,请创建两个单独的链,然后将它们连接到第三个链中:

WorkContinuation chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(chain1, chain2)
    .then(workE);
chain3.enqueue();

在这种情况下,WorkManager在workB之前运行workA。它还在workD之前运行workC。在workB和workD完成后,WorkManager运行workE。

注意:虽然WorkManager按顺序运行每个子链,但无法保证chain1中的任务可能与chain2中的任务重叠。例如,workB可能在workC之前或之后运行,或者它们可能同时运行。唯一的承诺是每个子链中的任务将按顺序运行;也就是说,workB在workA完成之后才会启动。

WorkContinuation方法有许多变体可以为特定情况提供缩写。例如,有一个WorkContinuation.combine(OneTimeWorkRequest,WorkContinuation …)方法,它指示WorkManager完成所有指定的WorkContinuation链,然后使用指定的OneTimeWorkRequest完成。有关详细信息,请参阅WorkContinuation参考

唯一的工作序列

您可以通过调用beginUniqueWork()而不是beginWith()来创建一个唯一的工作序列。每个独特的工作序列都有一个名称; WorkManager一次只允许一个具有该名称的工作序列。创建新的唯一工作序列时,如果已经有一个具有相同名称的未完成序列,则指定WorkManager应执行的操作:

取消现有序列并将其替换为新序列
保留现有序列并忽略您的新请求
将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务
如果您的任务不应多次排队,则唯一的工作顺序非常有用。例如,如果您的应用需要将其数据同步到网络,您可以将名为“sync”的序列排队,并指定如果已经有一个具有该名称的序列,则应忽略您的新任务。如果您需要逐步建立一长串任务,那么独特的工作顺序也很有用。例如,照片编辑应用程序可能允许用户撤消一长串操作。每个撤消操作可能需要一段时间,但必须以正确的顺序执行。在这种情况下,应用程序可以创建“撤消”链并根据需要将每个撤消操作附加到链。

标记的工作

您可以通过为任何WorkRequest对象分配标记字符串来逻辑地对任务进行分组。要设置标记,请调用WorkRequest.Builder.addTag(),例如:

OneTimeWorkRequest cacheCleanupTask =
        new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class)
    .setConstraints(myConstraints)
    .addTag("cleanup")
    .build();

WorkManager类提供了几种实用程序方法,使您可以使用特定标记操作所有任务。例如,WorkManager.cancelAllWorkByTag()取消具有特定标记的所有任务,WorkManager.getStatusesByTag()返回具有该标记的所有任务的所有WorkStatus的列表。

输入参数和返回值

为了获得更大的灵活性,您可以将参数传递给任务并让任务返回结果。传递和返回的值是键值对。要将参数传递给任务,请在创建WorkRequest对象之前调用WorkRequest.Builder.setInputData()方法。该方法采用Data对象,您使用Data.Builder创建。 Worker类可以通过调用Worker.getInputData()来访问这些参数。要输出返回值,任务调用Worker.setOutputData(),它接受一个Data对象;您可以通过观察任务的LiveData 来获取输出。

例如,假设您有一个执行耗时计算的Worker类。以下代码显示了Worker类的外观:

// Define the Worker class:
public class MathWorker extends Worker {

    // Define the parameter keys:
    public static final String KEY_X_ARG = "X";
    public static final String KEY_Y_ARG = "Y";
    public static final String KEY_Z_ARG = "Z";
    // ...and the result key:
    public static final String KEY_RESULT = "result";

    @Override
    public Worker.Result doWork() {


        // Fetch the arguments (and specify default values):
        int x = getInputData().getInt(KEY_X_ARG, 0);
        int y = getInputData().getInt(KEY_Y_ARG, 0);
        int z = getInputData().getInt(KEY_Z_ARG, 0);

        // ...do the math...
        int result = myCrazyMathFunction(x, y, z);

        //...set the output, and we're done!
        Data output = new Data.Builder()
            .putInt(KEY_RESULT, result)
            .build();
        setOutputData(output);
        return Result.SUCCESS;
    }
}

创建Work并传递参数,你可以使用这样的代码:

// Create the Data object:
Data myData = new Data.Builder()
    // We need to pass three integers: X, Y, and Z
    .putInt(KEY_X_ARG, 42)
    .putInt(KEY_Y_ARG, 421)
    .putInt(KEY_Z_ARG, 8675309)
    // ... and build the actual Data object:
    .build();

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class)
        .setInputData(myData)
        .build();
WorkManager.getInstance().enqueue(mathWork);

返回的值将在任务的WorkStatus中可用:

WorkManager.getInstance().getStatusById(mathWork.getId())
    .observe(lifecycleOwner, status -> {
         if (status != null && status.getState().isFinished()) {
           int myResult = status.getOutputData().getInt(KEY_RESULT,
                  myDefaultValue));
           // ... do something with the result ...
         }
    });

如果链接任务,则一个任务的输出可用作链中下一个任务的输入。 如果它是一个简单的链,只有一个OneTimeWorkRequest后跟另一个OneTimeWorkRequest,第一个任务通过调用setOutputData()返回其结果,下一个任务通过调用getInputData()获取该结果。 如果链更复杂 - 例如,因为几个任务都将输出发送到单个后续任务 - 您可以在OneTimeWorkRequest.Builder上定义一个InputMerger,以指定当不同任务返回具有相同键的输出时应该发生什么。

其他资源

WorkManager是一个Android Jetpack架构组件。 在Sunflower演示应用程序中查看它的使用情况。