Android WorkManager官方指南

1. 使用WorkManager调度任务

WorkManager API可以方便地指定可延迟的异步任务以及何时运行它们。这些API允许您创建任务并将其交给WorkManager立即运行或在适当的时机运行。

WorkManager根据设备API级别和应用程序状态等因素选择适当的方法来运行您的任务。如果WorkManager在应用程序运行时执行您的任务之一,那么WorkManager可以在应用程序的进程中的新线程中运行您的任务。如果您的应用程序没有运行,WorkManager会选择适当的方式来调度后台任务——这取决于设备API级别和包含的依赖,WorkManager可以使用JobSchedulerFirebase JobDispatcherAlarmManager。您不需要编写设备逻辑来确定设备具有什么功能,并选择合适的API;相反,您可以将任务交给WorkManager,让它选择最佳选项。

注意:WorkManager用于需要保证即使应用程序退出系统也会运行它们的任务,比如将应用程序数据上传到服务器。它不是用于进程内后台工作,如果应用程序进程消失了,可以安全地终止;对于这样的情况,我们建议使用线程池

1.1 主题

WorkManager基础

使用WorkManager在您选择的环境下调度单个任务,或者以指定的间隔重复运行任务。

1.2 WorkManager高级功能

设置链式任务序列,设置传递和返回值的任务,并设置命名的、独特的工作序列。

1.3 附加资源

WorkManager是Android Jetpack架构组件。在Sunflower演示应用程序中使用它。

2. WorkManager基础

使用WorkManager,您可以轻松地设置一个任务并将其交给系统,该任务可以在您指定的条件下运行。

这个概述涵盖了最基本的WorkManager特性。在这个页面中,您将学习如何设置任务,指定它应该运行的条件,并将其交给系统。您还将学习如何设置重复作业。

有关更高级的WorkManager特性的信息,如作业链接以及传递和返回值,请参阅WorkManager高级特性。还有更多可用的特性;对于详细信息,请参阅WorkManager参考文档

注意:要将WorkManager库导入到Android项目中,请参见将组件添加到项目中

2.1 类与概念

WorkManager API使用了几个不同的类。在某些情况下,您需要对API类中的一个子类进行分类。

这些是最重要的WorkManager类:

Worker:指定需要执行的任务。WorkManager APIs包括抽象Worker类。您继承此类并在此处执行工作。

WorkRequest:表示一个单独的任务。至少,WorkRequest对象指定哪个Worker类应该执行任务。但是,您还可以向WorkRequest对象添加细节,指定任务应该在哪些环境下运行。每个WorkRequest都有一个自动生成的惟一ID;可以使用该ID执行诸如取消队列中的任务或获取任务的状态之类的操作。WorkRequest是一个抽象类;在代码中,您将使用某个子类,OneTimeWorkRequestPeriodicWorkRequest

WorkRequest.Builder:创建WorkRequest对象的帮助类。再次,您将使用一个子类,OneTimeWorkRequest.BuilderPeriodicWorkRequest.Builder

Constraints:指定限制任务什么时候运行(例如,“仅当设备连接到网络时”)。你通过创建Constraints.Builder创建 Constraints对象。在创建WorkRequest之前,传递Constraints 对象给WorkRequest.Builder。

WorkManager:对工作请求进行排队和管理。您将WorkRequest对象传递给WorkManager来执行任务。WorkManager以分散系统资源负载的方式调度任务,同时遵守您指定的约束。

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

2.2 典型工作流

假设你正在编写一个photo library app,并且该应用程序需要周期性地压缩其存储的图像。您希望使用WorkMeaveAPI来安排图像压缩。在这种情况下,您并不特别关心压缩什么时候发生;您想设置任务并忘记它。

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

KOTLIN

class CompressWorker : Worker()  {

    override fun doWork(): Result {

        // 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.)

    }

}

JAVA

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将任务排到队列中:
KOTLIN

val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>().build()
WorkManager.getInstance().enqueue(compressionWork)

JAVA

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

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

KOTLIN

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

JAVA

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

有关使用LiveData的更多信息,请参见LiveData概述

2.3 任务约束

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

KOTLIN

// Create a Constraints object that defines when the task should run
val myConstraints = 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
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>()
        .setConstraints(myConstraints)
        .build()

JAVA

// Create a Constraints object 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在查找运行任务的时间时会考虑约束。

2.4 取消任务

您可以在任务加入队列后取消任务。要取消任务,您需要它的工作ID,您可以从WorkRequest对象中获取它。例如,下面的代码取消了前一节的压缩工作请求:

KOTLIN

val compressionWorkId:UUID = compressionWork.getId()
WorkManager.getInstance().cancelWorkById(compressionWorkId)

JAVA

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

WorkManager尽最大努力取消任务,但这样存在很大的不确定性——当您试图取消任务时,任务可能已经运行或完成。WorkManager还提供方法,以取消唯一工作序列中的所有任务,或者取消具有指定标记的所有任务,这也是尽最大努力取消任务的基础上。

2.5 Tagged work

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

KOTLIN

val cacheCleanupTask =
        OneTimeWorkRequestBuilder<MyCacheCleanupWorker>()
    .setConstraints(myConstraints)
    .addTag("cleanup")
    .build()

JAVA

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

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

2.6 周期性任务

您可能有一个需要重复执行的任务。例如,照片管理器APP压缩照片可能不止一次。更有可能的是,它会经常检查其共享照片,并查看是否有新的或更改的图像需要压缩。这个重复的任务可以压缩它找到的图像,或者,当它找到需要压缩的图像时,它可以启动新的“压缩此图像”任务。

要创建循环任务,请使用PeriodicWorkRequest.Builder类创建PeriodicWorkRequest对象,然后以与OneTimeWorkRequest对象相同的方式将PeriodicWorkRequest加入队列。例如,假设我们定义了一个PhotoCheckWorker类来识别需要压缩的图像。如果您想每12小时运行一次清单任务,您将创建一个像这样的PeriodicWorkRequest对象:

KOTLIN

val photoCheckBuilder =
        PeriodicWorkRequestBuilder<PhotoCheckWorker>(12, TimeUnit.HOURS)
// ...if you want, you can apply constraints to the builder here...

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

JAVA

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试图在您请求的时间间隔内运行您的任务,但要遵守您施加的约束以及其它要求。

3. WorkManager高级主题

WorkManager使建立和预约复杂的任务请求变得容易。您可以使用API来实现这样的场景:

3.1 Chained tasks

应用程序可能需要按特定顺序运行多个任务。WorkManager允许您创建多个任务并将其加入工作序列中,并指定它们应该运行的顺序。

例如,假设应用程序有三个OneTimeWorkRequest对象:WorkA、WorkB和WorkC。任务必须按该顺序运行。要使它们入队,使用WorkManager.beginWith()方法创建一个序列,传递第一个OneTimeWorkRequest对象;该方法返回一个WorkContinuation对象,该对象定义一系列任务。然后按顺序使用WorkContinuation.then()添加剩余的OneTimeWorkRequest对象,最后使用WorkContinuation.enqueue()对整个序列进行入队:

KOTLIN

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()

JAVA

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在运行序列的其余部分之前(并行地)运行所有这些任务。例如:

KOTLIN

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()

JAVA

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()方法将多个链合并来创建更复杂的序列。例如,假设您想运行这样的序列:

image

图1 可以使用WorkContinuation设置复杂链式任务。

要设置这个序列,创建两个独立的链,然后将它们组合成第三个:

KOTLIN

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

JAVA

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();

在这种情况下,WorkMeor在工作B之前运行WorkA。它还运行WorkC之前Word。工作和工作完成后,WorkMeor运行WORKE。

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

存在一些特定情况,有一系列WorkContinuation方法提供短时处理。例如,有一个WorkContinuation.combine(OneTimeWorkRequest,WorkContinuation…)方法,它指示WorkManager完成所有指定的WorkContinuation链,然后以指定的OneTimeWorkRequest结束。有关详细信息,请参见WorkContinuation。

3.2 Unique工作序列

您可以创建一个Unique的工作序列,通过调用beginUniqueWork()而不是beginWith()启动序列。每个唯一的工作序列都有一个名称;WorkManager只允许一次使用一个名称的工作序列。在创建新的唯一工作序列时,指定如果已经存在具有相同名称的未完成序列,则WorkManager应该做什么:

  • 取消现有的序列并将其替换为新的序列

  • Keep现有的顺序,忽略您的新请求

  • 将新序列Append到现有序列上,在现有序列的最后一个任务完成后运行新序列的第一个任务

如果您有一个不应该多次排队的任务,那么Unique工作序列可能是有用的。例如,如果您的app需要将其数据同步到网络,则可以将名为“sync”的序列加入队列,并指定如果已经存在具有该名称的序列,则应忽略新任务。如果你需要逐步建立一个长的任务链,那么Unique工作序列也是有用的。例如,一个图片编辑应用程序可以让用户撤消一长串的动作。每一个撤消操作可能需要一段时间,但它们必须按正确的顺序执行。在这种情况下,应用程序可以创建一个"undo"链,并根据需要将每个撤销操作附加到链上。

3.3 输入参数和返回值

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

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

KOTLIN

// Define the parameter keys:
const val KEY_X_ARG = "X"
const val KEY_Y_ARG = "Y"
const val KEY_Z_ARG = "Z"

// ...and the result key:
const val KEY_RESULT = "result"

// Define the Worker class:
class MathWorker : Worker()  {

    override fun doWork(): Result {
        val x = inputData.getInt(KEY_X_ARG, 0)
        val y = inputData.getInt(KEY_Y_ARG, 0)
        val z = inputData.getInt(KEY_Z_ARG, 0)

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

        //...set the output, and we're done!
        val output: Data = mapOf(KEY_RESULT to result).toWorkData()
        setOutputData(output)

        return Result.SUCCESS
    }
}

JAVA

// 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并传递参数,您将使用这样的代码:

KOTLIN

val myData: Data = mapOf("KEY_X_ARG" to 42,
                       "KEY_Y_ARG" to 421,
                       "KEY_Z_ARG" to 8675309)
                     .toWorkData()

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(myData)
        .build()
WorkManager.getInstance().enqueue(mathWork)

JAVA

// 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中返回的值是可用:

KOTLIN

WorkManager.getInstance().getStatusById(mathWork.id)
        .observe(this, Observer { status ->
            if (status != null && status.state.isFinished) {
                val myResult = status.outputData.getInt(KEY_RESULT,
                      myDefaultValue)
                // ... do something with the result ...
            }
        })

JAVA

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,以指定在不同的任务返回具有相同键的输出时应该发生什么。

3.4 附加资源

WorkManager是Android Jetpack架构组件。在Sunflower demo app中使用它。

猜你喜欢

转载自blog.csdn.net/u011897062/article/details/82230289