Android WorkManager的使用

3,016 阅读6分钟

1. 简介

当我们想要实现不是很紧急但是需要必须完成的处理时会比较难办,如果使用service会产生额外的电量消耗,如果使用Broadcast比较难实现以及还需要设置触发条件。为了解决这个问题谷歌在Android 5.0时推出了JobScheduler,来替换此前的方案。 作为更进一步在Android Jetpack上推出了WorkManager, 它会根据系统的版本用不同的方法去实现上述需求。

2. WorkManager的简单介绍

WorkManager 是一个 Android 库, 它在工作的触发器 (如适当的网络状态和电池条件) 满足时, 优雅地运行可推迟的后台工作。WorkManager 尽可能使用框架 JobScheduler , 以帮助优化电池寿命和批处理作业。在 Android 6.0 (API 级 23) 下面的设备上, 如果 WorkManager 已经包含了应用程序的依赖项, 则尝试使用Firebase JobDispatcher 。否则, WorkManager 返回到自定义 AlarmManager 实现, 以优雅地处理您的后台工作。

根据官方的叙述,WorkManager会根据限制条件自动进行后台任务。如果系统版本在6.0以上则会使用JobScheduler,如果是一下则会使用AlarmManager+BroadCastReceiver

3. 使用方法

3.1 添加依赖

在module的build.gradle中添加如下依赖。

implementation "androidx.work:work-runtime-ktx:2.3.3"

3.2 创建后台处理

我们首先需要创建需要被执行的任务。我们需要继承Worker类,并且需要重写doWork方法。

class CustomWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {
    override fun doWork(): Result {
        Log.d("CustomWorker", "Worker is active in " + inputData.getString("data"))
        return Result.success()
    }
}

inputData.getString(key)是获取外部的传值。使用方法跟intent传值类似。 我们需要返回的值是Result的枚举值。一共有如下几种。

  1. Result.success() 表示任务执行成功。
  2. Result.retry() 表示任务执行失败,需要再次尝试。可以在后面讲述的退避规则BackoffPolicy的设定方法进行尝试。
  3. Result.failure() 表示任务执行失败,但不再进行尝试。

3.3 创建触发时的限制条件

我们需要通过Constrains来设置触发时的限制条件。

        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .setRequiresStorageNotLow(true)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(true)
            .build()
3.3.1 setRequiredNetworkType

设置需要的网络类型。一共有如下几种。

枚举值 状态说明
NOT_REQUIRED 不需要网络
CONNECTED 任何可用网络
UNMETERED 需要不计量网络,如WiFi
NOT_ROAMING 需要非漫游网络
METERED 需要计量网络,如4G
3.3.2 setRequiresBatteryNotLow

设置手机电量的状态,这里的条件是执行任务时手机电量不能较低。

3.3.3 setRequiresStorageNotLow

设置手机内存空间的状态,这里的条件是执行任务时手机内存空间不能较低。

3.3.4 setRequiresCharging

设置手机的充电状态, 这里的条件是手机执行任务时手机是处于充电的状态。

3.3.5 setRequiresDeviceIdle

设置手机的状态, 这里的条件是手机执行任务时手机是出于空闲状态。

3.3.6 addContentUriTrigger(uri:Uri, triggerForDescendants:Boolean)

添加触发器,即当指定的URI中有内容更新时会触发任务。值得注意的是这里只能在OneTimeWorkRequest中设置。

3.4 创建WorkRequest

一共有两种WorkRequest。一种是只执行一次的OneTimeWorkRequest,还有一种是定期执行的PeriodicWorkRequest

val oneTimeWorkRequest =
OneTimeWorkRequestBuilder<CustomWorker>().setConstraints(constraints)
    .setBackoffCriteria(
        BackoffPolicy.LINEAR,
        OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
        TimeUnit.MILLISECONDS
    )
    .setInputData(inputData)
    .build()

val periodicWorkRequest =
PeriodicWorkRequestBuilder<CustomWorker>(10, TimeUnit.MILLISECONDS)
    .setConstraints(constraints)
    .setBackoffCriteria(
        BackoffPolicy.LINEAR,
        PeriodicWorkRequest.MIN_BACKOFF_MILLIS,
        TimeUnit.MILLISECONDS
    ).build()

PeriodicWorkRequest的构造器中还需要添加执行的周期,上述代码是每10秒去确认条件是否执行。但是周期任务最少间隔是15分钟,所以虽然是写了10秒,但是会在15分钟后会被执行。

3.4.1 setConstraints

我们要在WorkRequest中设置Constraints(即上面讲过的限制条件)。

3.4.2 setBackoffCriteria

我们还可以设置退避条件。 退避条件有两个属性:

  1. BackoffPolicy, 默认为是指数性的,但是可以设置成线性。
  2. 持续时间, 默认为30秒。
3.4.3 setInputData

我们可以在WorkRequest中通过setInputData方法给Worker传值。使用方法跟Intent相似,如下。

 val inputData = Data.Builder().putString("data", "MainActivity").build()

3.5 通过WorkManager执行

最后我们可以通过WorkManager去执行上面制定的各种需求。

WorkManager.getInstance(this).enqueue(oneTimeWorkRequest)

3.6 监视Worker执行情况

我们还可以通过LiveDataWorker的执行情况进行监视。当Worker被执行时,监视处会受到通知。

WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.id).observe(this,
    Observer {
        count++
        txtId.text = "Work status is changed! $count"
        Toast.makeText(this, "Work status is changed!",Toast.LENGTH_LONG).show()
    })

WorkInfo的源代码如下:

private @NonNull UUID mId;
    private @NonNull State mState;
    private @NonNull Data mOutputData;
    private @NonNull Set<String> mTags;
    private @NonNull Data mProgress;
    private int mRunAttemptCount;

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkInfo(
            @NonNull UUID id,
            @NonNull State state,
            @NonNull Data outputData,
            @NonNull List<String> tags,
            @NonNull Data progress,
            int runAttemptCount) {
        mId = id;
        mState = state;
        mOutputData = outputData;
        mTags = new HashSet<>(tags);
        mProgress = progress;
        mRunAttemptCount = runAttemptCount;
    }

首先看一下State

public enum State {

    ENQUEUED,//加入队列
    RUNNING,//运行中
    SUCCEEDED,//已成功
    FAILED,//失败
    BLOCKED,//挂起
    CANCELLED;//取消

    public boolean isFinished() {
        return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
    }
}

BLOCKED的情况是,当约束情况没有被全部满足是Worker会被挂起。

3.7 结束任务

WorkRequest入列了以后,WorkManager会为它分配一个work ID,我们可以通过work ID进行任务的取消或停止。

WorkManager.getInstance(this).cancelWorkById(workRequest.id)

WorkManager并不一定能结束任务,因为有些任务可能已经执行完毕 除了上述方法以外还有其他结束任务的方法:

  1. cancelAllWork(): 取消所有的任务
  2. cancelAllWorkByTag(tag:String): 取消一组带有相同标签的任务
  3. cancelUniqueWoork(uniqueWorkName:String): 取消唯一任务

3.8 链式调用

当我们需要按顺序或者同时执行WorkRequest时,我们可以用链式调用的方法执行。

3.8.1并行执行
val workRequest1 = OneTimeWorkRequestBuilder<CustomWorker>().build()
val workRequest2 = OneTimeWorkRequestBuilder<CustomWorker>().build()
val workRequest3 = OneTimeWorkRequestBuilder<CustomWorker>().build()

// 会同时执行。
WorkManager.getInstance().beginWith(workRequest1,workRequest2,workRequest3).enqueue()
3.8.2顺序执行

我们可以使用beginWith-then方法进行顺序执行。如果顺序执行的途中有一个失败了,之后的任务将不会被执行。

// 顺序执行
WorkManager.getInstance().beginWith(workRequest1).then(workRequest2).then(workRequest3).enqueue()
3.8.3 组合执行

我们可以使用combine操作符进行组合执行。

// 组合执行
val chain1 = WorkManager.getInstance()
    .beginWith(workRequest1)
    .then(workRequest2)
val chain2 = WorkManager.getInstance()
    .beginWith(workRequest3)
    .then(workRequest4)
WorkContinuation
    .combine(chain1, chain2)
    .then(workRequest5)
    .enqueue()
3.8.4 唯一链

唯一链,如其名就是同一时间内在执行队列中农不能存在相同名称的任务。

val workRequest = OneTimeWorkRequestBuilder<CustomWorker>().build()
WorkManager.getInstance(this).beginUniqueWork("TAG", ExistingWorkPolicy.REPLACE, workRequest)

注意的是最有一个参数WorkRequest可变长度的参数。 第一个参数就是任务名字"TAG"。 第二个参数是已存在任务时的执行策略。枚举值如下。

public enum ExistingWorkPolicy {
    REPLACE, // 替换
    KEEP, // 保持,不做任何操作
    APPEND // 添加到已有的任务中
}
  1. REPLACE: 存在相同名称的任务并且被挂起,则取消和删除现有的任务,然后替换新的任务。
  2. KEEP: 存在相同名称的任务并且被挂起,则不做任何操作。
  3. APPEND: 存在相同名称的任务并且被挂起,会把新任务添加到缓存中,都队列中的所有任务都被执行完毕时,新任务会被设为第一任务。

github: github.com/HyejeanMOON…