完整体验 Jetpack WorkManager 及场景探讨

Posted 涂程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了完整体验 Jetpack WorkManager 及场景探讨相关的知识,希望对你有一定的参考价值。

作者:chenfp

WorkManagerJetpack 系列中非常重要的一个框架,可以非常方便地帮助开发者实现即使在退出应用或重启设备后仍得以运行的可靠异步任务

WorkManager 可以用来替换所有先前的 android 后台调度 API(包括 FirebaseJobDispatcherGcmNetworkManagerJobScheduler)的组件,并且在开发时就考虑到了对电池续航的影响,进行了优化。

WorkManager 的适用场景

  1. 延时任务:WorkManager 允许对 Worker 设置一个延时,所以可以非常方便的执行延时任务。

  2. 长时任务:对于短时间的任务,我们可以使用其他的方法来执行。但是对于长时间,并且当应用处于后台或者重启之后还执行的任务,推荐使用 WorkManager。

注意:WorkManager 最早兼容到 API 14(Android 4.0)。

Worker 任务

对于 WorkManager 来说,Worker 是一个非常重要的类。我们执行的任务就是定义在 Worker 中。我们执行延时、约束等也是对于 Worker 的操作。

Worker 的创建

导入库

dependencies 
    def work_version = "2.5.0" 
    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional - GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"

    // optional - Multiprocess support
    implementation "androidx.work:work-multiprocess:$work_version"

定义工作

扩展 Worker 类并替换 doWork() 方法。

public class UploadWorker extends Worker 
   public UploadWorker(
       @NonNull Context context,
       @NonNull WorkerParameters params) 
       super(context, params);
   

   @Override
   public Result doWork() 

     // Do the work here
     XXXXXXXXXX

     // Indicate whether the work finished successfully with the Result
     return Result.success();
   

WorkRequest 任务请求

上面我们知道了定义一个 Worker 是怎么实现的了,那么延时,约束是如何实现的呢?这边我们就需要了解 WorkRequest 这个类了。

WorkRequest 可以帮助我们对 Worker 进行延时、约束、定义一次或者重复性 Worker。

OneTimeWorkRequest 一次性请求

对于无需额外配置的简单工作,请使用静态方法 from

WorkRequest uploadWorkRequest = OneTimeWorkRequest.from(UploadWorker.class);

对于更复杂的工作,可以使用构建器。

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(UploadWorker.class)
       // Additional configuration
       .build();

PeriodicWorkRequest 重复性请求

您的应用有时可能需要定期、重复运行某些工作。例如,您可能要定期备份数据、定期下载应用中的新鲜内容或者定期上传日志到服务器。

PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS) //工作的运行时间间隔定为一小时
           // Constraints
           .build();

备注:可以定义的最短重复间隔是 15 分钟

延时任务

下面举例说明了如何将工作设置为在加入队列后至少经过 10 分钟 后再运行。

WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

该示例说明了如何为 OneTimeWorkRequest 设置初始延迟时间,您也可以为 PeriodicWorkRequest 设置初始延迟时间。在这种情况下,定期工作只有首次运行时会延迟。

约束任务

约束可确保将工作延迟到满足最佳条件时运行。以下约束适用于 WorkManager。

NETWORKTYPE约束运行工作所需的网络类型。例如 WI-FI (UNMETERED)。
BatteryNotLow如果设置为 true,那么当设备处于“电量不足模式”时,工作不会运行。
RequiresCharging如果设置为 true,那么工作只能在设备充电时运行。
DeviceIdle如果设置为 true,则要求用户的设备必须处于空闲状态,才能运行工作。如果您要运行批量操作,否则可能会降低用户设备上正在积极运行的其他应用的性能,建议您使用此约束。
StorageNotLow如果设置为 true,那么当用户设备上的存储空间不足时,工作不会运行。

创建一组约束并将其与某项工作相关联,请使用一个 Contraints.Builder() 创建 Constraints 实例,并将该实例分配给 WorkRequest.Builder()

例如,以下代码会构建了一个工作请求,该工作请求仅在用户设备 正在充电且连接到 Wi-Fi 网络时 才会运行:

Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)  //设置连接到WiFi
       .setRequiresCharging(true)  //设置正在充电
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();
  • 如果指定了多个约束,工作将仅在满足所有约束时才会运行。

  • 如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。

Worker 提交系统

使用 WorkManager 提供的 enqueue() 方法将 WorkRequest 提交到系统。

WorkManager
    .getInstance(myContext)
    .enqueue(uploadWorkRequest);

唯一 Worker

当把工作加入队列的最简单的方法就是调用 enqueue() 方法,但是这种情况下,有可能将同一个作业多次加入到队列中,所以你可以通过创建一个唯一工作序列,确保同一时刻只有一个具有特定名称的工作实例。

与 ID 不同,唯一名称是开发者指定的,而不是由 WorkManager 自动生成的;与Tag也不同,唯一名称仅与一个工作实例相关联。

创建唯一工作的方法:

  • WorkManager.enqueueUniqueWork(用于一次性工作)
  • WorkManager.enqueueUniquePeriodicWork(用于定期工作)

这两种方法都有3个参数:

  • uniqueWorkName - 用于标识唯一工作请求的 String
  • existingWorkPolicy - 用于如果已有使用该名称且尚未完成的唯一工作(链),应该执行什么操作。参照冲突解决政策
  • workRequest - 需要调度的 WorkRequest
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

冲突解决政策

调度唯一工作时,您必须告知 WorkManager 在发生冲突时要执行的操作。您可以通过在将工作加入队列时传递一个枚举来实现此目的。

对于一次性工作,您需要提供一个 ExistingWorkPolicy,它有4个选项。

  • REPLACE : 用新工作替换现有工作。此选项将取消现有工作
  • KEEP : 保留现有工作,并忽略新工作
  • APPEND : 将新工作附加到现有工作的末尾。此选项会将新工作链接到现有工作,并在现有工作完成后运行。并且现有工作将成为新工作的先决条件。如果现有工作为 CANCELLED 或者 FAILED 状态,新工作也会变为 CANCELLED 或者 FAILED 状态
  • APPEND_OR_REPLACE : 此选项类似于 APPEND,不过它不依赖于现有工作的状态。即使现有工作为 CANCELLED 或者 FAILED 状态,新工作仍旧会运行

对于定期工作,需要提供 ExistingPeriodicWorkpolicy,它支持 REPLACE 和 KEEP 两个选项。这些选项的功能和对应的 ExistingWorkPolicy 功能相同。

Worker 交互

输入输出

在输入和输出中,输入值以键值对的形式存储在 Data 对象中,并且可以在工作请求中设置。WorkManager 会在执行工作时将输入 Data传递给工作。Worker 类可通过调用Worker.getInputData() 访问输入参数。

String[] strings = "AAA","BBB","CCC","DDD";
Data data = new Data.Builder().putStringArray("STRING_ARRAY_KEY", strings).build();
WorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadWorker.class)
        .setInputData(data)  //输入
        .build();

WorkManager.getInstance(getBaseContext()).enqueue(uploadWorkRequest);      WorkManager.getInstance(getBaseContext()).getWorkInfoByIdLiveData(uploadWorkRequest.getId())
        .observe(this, info -> 
                if (info != null && info.getState() == WorkInfo.State.SUCCEEDED) 
                    String[] myResult = info.getOutputData().getStringArray("STRING_ARRAY_KEY_OUT");  //输出
                    
                    // ... do something with the result ...
                    String string = "";
                    Log.d("test","myResult is " + myResult.toString());
                    for (int i = 0; i < myResult.length; i++) 
                        string = string + myResult[i] + "\\n";
                    
                textView.setText(string);
            
        
);

注意:通过 Data 这种方式的输入输出只适用于 OneTimeWorkRequest。对于不适用于 PeriodicWorkRequest 的原因请参照:WorkManager :创建&提交&输入输出&取消 - 掘金 (juejin.cn)。

监视

在将工作加入队列后,你可以额随时按其 name、id 或者与其关联的 tag 在 WorkManager 中进行查询,和检查其状态。

// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>

该查询会返回 WorkInfo 对象的 ListenableFuture,该值包含工作的 id、标记、当前的 state 和通过 Result.success(outputData) 设置的任何输出数据。

Worker 的取消

在 WorkManager 中存在如下的四个 cancel 方法:cancelAllWork、cancelAllWorkByTag、cancelWorkById 和 cancelUniqueWork。

cancelAllWork()

该函数将 cancel 所有的没有 finish 的 work。

cancelAllWorkByTag(String tag)

该函数将 cancel 所有的没有 finish 的并且 tag 符合给定的 work。

cancelWorkById(UUID id)

该函数将 cancel 没有 finish 的并且 id 符合给定的 work。

Worker 的状态

一次性工作的状态

对于 one-time 工作请求,工作的初始状态为 ENQUEUED。

在 ENQUEUED 状态下,你的工作会在满足工作约束和工作延迟的要求后立即执行。然后工作会转为 RUNNING 状态,再根据工作的结果转为 SUCCEEDED、FAILED 状态;或者,如果结果是 retry,它可能会回到 ENQUEUED 状态。在这个过程中,你可以随时取消工作,取消后工作将进入 CANCELLED 状态。

SUCCEEDED、FAILED 和 CANCELLED 都表示此工作的终止状态,并且 WorkInfo.State.isFinished() 都返回 true。

重复工作的状态

对于 PeriodicWorkRequest 来说,只有一个终止状态 CANCELLED。SUCCEEDED 和 FAILED 仅仅适用于 one-time 工作。这是因为定期工作除了取消工作之外,永远都不会结束。每次运行结束之后,无论结果如何,系统都会重新对其调度。

工作链

WorkManager 允许定义多个 Worker,然后组成一个链式结构进行运行。最简单的工作链只需要通过beginWith(worker/arraylist<worker>), then(worker/arraylist<worker>)两个简单的方法就可以实现。例如:

WorkManager.getInstance(myContext)
    // 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 parallel):
    .then(Arrays.asList(workC1, workC2))
    .enqueue();

对于复杂一点的工作链,可以通过WorkContinuation来定义多个子链,然后再组成一个父链来提交系统执行。

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

需要提醒的是:WorkContinuation 定义的子链不能保证执行的先后顺序,只能保证子链中的 worker 是按顺序执行的。

以上是关于完整体验 Jetpack WorkManager 及场景探讨的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack之WorkManager源码分析

Android Jetpack WorkManager 介绍

Android Jetpack WorkManager 参数传递

Jetpack WorkManager 看这一篇就够了~

Android Jetpack 从使用到源码深耕调度任务组件WorkManager 从实践到原理

Android Jetpack系列 之 WorkManager