WorkManager-Guide&Tips

栏目: Java · 发布时间: 6年前

内容简介:注意一下, 同样是后台线程,如果对更好用的 WorkManager 感兴趣, 就可以继续往下看了. 大概的介绍顺序是:

WorkManager 为了方便运行一些 不着急的异步的后台 任务而诞生. 大部分情况下, 只需要定义好自己想做的任务, 交给 WorkManager 去执行, 剩下就不用管了.

注意一下, 同样是后台线程, WorkManager 的重点在于保证 就算 App 关掉之后后台任务也能够被执行 . 而那种可以随着 App 退出而关闭的后台任务, 还是更适合使用 ThreadPools .

以前的实现方案

  1. Service: 这是最常见的需要后台运行的方案了. 对比来说, Service 有以下几个问题:

    • 可能会由于开发者的设置而疯狂运行, 这会导致手机电量被疯狂消耗. 对比之下 WorkManager 的同一个周期任务的最小间隔时间是15分钟.
    • targetSdkVersion 为 26 及以上的时候, 在不被允许创建后台服务的情况下, startService() 会抛出 IllegalStateException . 对比之下 WorkManager 会按照设定选择合适的时间运行.
  2. JobScheduler: 这个最关键的就是只有 Android 5.0 以上才能使用, 其实 WorkManager 在 5.0 以上也是用这个实现的.

  3. AlarmManager + BroadcastReceiver. 这个方案也是可以的, WorkManager 在 5.0 以下也是这样实现的, 只是封装了更好用的 API .

如果对更好用的 WorkManager 感兴趣, 就可以继续往下看了. 大概的介绍顺序是:

  1. 导入
  2. 一次性任务的使用
  3. 周期性任务的使用
  4. 任务如何取消
  5. 给任务加上约束条件
  6. 多个任务以特定顺序执行
  7. 相同任务的重复处理策略
  8. 任务的输入和输出
  9. 一些需要注意的点

以下代码都可以在 Demo 中找到.

导入

和其他 JetPack 的组件一样, 在 projectbuild.gradle 文件中添加 google() 源:

allprojects {
    repositories {
        google()
        jcenter()
    }
}

然后在 modulebuild.gradle 中添加 WorkManager 的依赖:

dependencies {
    def work_version = "1.0.0-beta05"
    
    // Java 依赖版本
    implementation "android.arch.work:work-runtime:$work_version"
    
    // Kotlin 依赖版本, 和上面的依赖二选一即可
    implementation "android.arch.work:work-runtime-ktx:$work_version"
    
    // 可选 RxJava2 支持
    implementation "android.arch.work:work-rxjava2:$work_version"

    // 可选 测试支持
    androidTestImplementation "android.arch.work:work-testing:$work_version"
}

基本使用

使用起来就如上面所说, 首先你需要创建一个 任务 (Worker) , 然后丢给 WorkManager .

Kotlin
class TestWorker(context: Context, params: WorkerParameters)
    : Worker(context, params) {

    override fun doWork(): Result {
    
		// 这里已经是后台线程了, 只需要实现自己的业务逻辑就好了
		// return Result.retry(); 重试
		// return Result.failure(); 不再重试
    	return Result.success()
    }

}
Java
public class TestWorker extends Worker {

    public TestWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Result doWork() {

        // 这里已经是后台线程了, 只需要实现自己的业务逻辑就好了
        // return Result.retry(); 重试
        // return Result.failure(); 不再重试
        return Result.success();
    }
}

Worker 里面只声明要实现的任务, 其他的约束条件要在 WorkRequest 中设置, 把 Worker 变成 WorkRequest . 再交给 WorkManager 去执行就好了.

一次性任务

Kotlin
val oneTimeWorker = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
WorkManager.getInstance().enqueue(oneTimeWorker)
Java
OneTimeWorkRequest oneTimeWorker =
        new OneTimeWorkRequest.Builder(TestWorker.class).build();
WorkManager.getInstance().enqueue(oneTimeWorker);

就是这么简单, 接下来 Worker 就会在后台线程运行了.

周期性任务

周期性任务需要更加慎重一点. 开启之后如果不注意, 大部分情况下就会一直运行, 这可能带来很不好的用户体验.

设置周期性任务的时候, 需要设置 repeatInterval(重复区间)flexInterval(弹性区间) 参数, 配合注释说明:

[  弹性区间外  |  弹性区间内 (flex Interval) ][  弹性区间外  |  弹性区间内 (flex Interval) ]...
[  任务不运行  |          任务可运行         ][  任务不运行  |          任务可运行         ]...
\_________________________________________/\________________________________________/...
       第一个区间 (repeat Interval)                 第二个区间 (repeat Interval)        ...(repeat)

repeatInterval 最小值是15分钟, 而 flexInterval 的最小值是5分钟, 如果 flexInterval 大于 repeatInterval , 也会被修改到和 repeatInterval 一样的值.

取消任务

但是从 API 可以看到, WorkManager 是将这个 Worker 入队了, 那既然是以队列维护的异步操作, 肯定会有重复的问题. WorkManager 默认的操作是遇到一样的 Worker 时, 新 Worker 会等旧 Worker 运行完再运行, 即顺序执行.

不过大部分情况下这都不是我们想要的模式, 所以在运行前最好取消相同的任务. 每个 Worker 都有一个唯一标识 UUID, 同时在构建 WorkRequest 的时候还可以添加任意个 Tag, 通过这两个标识都可以取消任务.

Kotlin
// UUID 方式
val workId: UUID = oneTimeWorker.getId()
WorkManager.getInstance().cancelWorkById(workId)

// Tag 方式
val oneTimeWorker = OneTimeWorkRequest.Builder(TestWorker::class.java)
    .addTag("myTag")
    .build()
WorkManager.getInstance().cancelAllWorkByTag("myTag")
Java
// UUID 方式
UUID workId = oneTimeWorker.getId();
WorkManager.getInstance().cancelWorkById(workId);

// Tag 方式
OneTimeWorkRequest myTask = new OneTimeWorkRequest.Builder(TestWorker.class)
    .addTag("myTag")
    .build()
WorkManager.getInstance().cancelAllWorkByTag("myTag")

取消相同的任务已经避免了系统资源不必要的消耗, 不过为了防止 API 的滥用, 还推荐给任务加上一些约束条件, 方便任务在系统资源没那么紧张的时候再执行:

加上约束

所有的约束 Constraints 都是由 Constraints.Builder() 来创建的, Builder 提供了以下的约束方式:

Kotlin
// 设置网络类型
setRequiredNetworkType(networkType: NetworkType)
// 是否运行时电量不要太低
setRequiresBatteryNotLow(requiresBatteryNotLow: Boolean)
// 是否在充电时才运行
setRequiresCharging(requiresCharging: Boolean)
// 是否不太剩余存储空间过低时运行
setRequiresStorageNotLow(requiresStorageNotLow: Boolean)
// 是否在设备空闲时运行, 这个最低版本是 23
setRequiresDeviceIdle(requiresDeviceIdle: Boolean)
// 监听一个本地的 Uri, 第二个参数是否监听 Uri 的子节点. 在 Uri 的内容改变时运行任务, 最低版本是 24
addContentUriTrigger(uri: Uri, triggerForDescendants: Boolean)
Java
// 设置网络类型
setRequiredNetworkType(NetworkType networkType)
// 是否运行时电量不要太低
setRequiresBatteryNotLow(boolean requiresBatteryNotLow)
// 是否在充电时才运行
setRequiresStorageNotLow(boolean requiresStorageNotLow)
// 是否不太剩余存储空间过低时运行
setRequiresStorageNotLow(requiresStorageNotLow: Boolean)
// 是否在设备空闲时运行, 这个最低版本是 23
setRequiresDeviceIdle(boolean requiresDeviceIdle)
// 监听一个本地的 Uri, 第二个参数是否监听 Uri 的子节点. 在 Uri 的内容改变时运行任务, 最低版本是 24
addContentUriTrigger(Uri uri, boolean triggerForDescendants)

多个任务的执行顺序

WorkManager 提供了相应的 API 使任务可以使一个或多个 OneTimeWorkerRequest 按某个顺序执行.

// A, B, C 就会按顺序执行, 如果全部返回成功或者某一个返回失败, 那该任务链就会结束.
WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
    .then(workC)
    .enqueue()

// A, B 一起运行, 虽然这2个的开始顺序不定, 但是 C 一定是在这2个运行后才运行.
WorkManager.getInstance()
    .beginWith(Arrays.asList(workA, workB))
    .then(workC)
    .enqueue()
    
// B 一定会在 A 后面运行, D 也一定会在 C 后面运行, 但是 AB 与 CD 这两条链的运行顺序不定, 但是 E 一定是在 B 和 D 都结束后才运行.
val chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
val chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD)
val chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE)
chain3.enqueue()
// A, B, C 就会按顺序执行, 如果全部返回成功或者某一个返回失败, 那该任务链就会结束.
WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
    .then(workC)
    .enqueue();

// A, B 一起运行, 虽然这2个的开始顺序不定, 但是 C 一定是在这2个运行后才运行.
WorkManager.getInstance()
    .beginWith(Arrays.asList(workA, workB))
    .then(workC)
    .enqueue();
    
// B 一定会在 A 后面运行, D 也一定会在 C 后面运行, 但是 AB 与 CD 这两条链的运行顺序不定, 但是 E 一定是在 B 和 D 都结束后才运行.
WorkContinuation chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE);
chain3.enqueue();

相同任务的重复策略

前面提到对于 Worker 来说, 可以通过 UUID 和 Tag 来保证其唯一性, 这样在需要的时候就可以避免任务重复执行. 但对于连续的任务链, 如果任务多了, 这样的方式会很繁琐. 于是, WorkerManager 也提供了相应的 API 来保证其唯一性.

Kotlin
beginUniqueWork(uniqueWorkName: String, existingWorkPolicy: ExistingWorkPolicy, work: OneTimeWorkRequest): WorkContinuation

beginUniqueWork(uniqueWorkName: String, existingWorkPolicy: ExistingWorkPolicy, work: List<OneTimeWorkRequest>): WorkContinuation
Java
WorkContinuation beginUniqueWork(String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, OneTimeWorkRequest work)

WorkContinuation beginUniqueWork(String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, List<OneTimeWorkRequest> work)

第一个参数就是这一个或者一系列 worker 的名字, 第二个参数就是重复时的操作, 有以下几种模式:

  • ExistingWorkPolicy.APPEND : 如果上一个任务处于等待或者未完成的状态, 则把当前任务添加到其任务链的后面. 这样它就在上一个任务执行完后执行.
  • ExistingWorkPolicy.KEEP : 如果上一个任务处于等待或者未完成的状态, 什么都不做(继续等上一个任务执行).
  • ExistingWorkPolicy.REPLACE : 如果上一个任务处于等待或者未完成的状态, 取消并删除上一个, 执行新的.

输入和输出

Worker 的输入输出是用 Map<String, Object> 来存储的, 用 Data 类封装了一层. 输出用 LiveData 来监听.

Kotlin

// 创建输入
val inputData = Data.Builder()
                    .putInt("KEY_FIRST", firstNumber)
                    .putInt("KEY_SECOND", secondNumber)
                    .build()
val worker = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(inputData)
        .build()
WorkManager.getInstance().enqueue(worker)

// Worker 类:
class PlusWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    override fun doWork(): Result {
        val first = inputData.getInt("KEY_FIRST", 0)
        val second = inputData.getInt("KEY_SECOND", 0)
        val result = first + second // 1 + 2 = 3
        val output = Data.Builder()
                .putInt("KEY_RESULT", result)
                .build()
        return Result.success(output)
    }
}

// 监听返回
WorkManager.getInstance().getWorkInfoByIdLiveData(worker.id)
        .observe(this, Observer { info ->
            if (info != null && info.state.isFinished) {
               	// 获取返回结果, 应该是3
                val result = info.outputData.getInt("KEY_RESULT", 0)
            }
        })
Java
// 创建输入
Data inputData = new Data.Builder()
    .putInt("KEY_FIRST", 1)
    .putInt("KEY_SECOND", 2)
    .build();

OneTimeWorkRequest worker = new OneTimeWorkRequest.Builder(PlusWorker.class)
        .setInputData(inputData)
        .build();
WorkManager.getInstance().enqueue(worker);

// Worker 类:
public class PlusWorker extends Worker {

    public PlusWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        int first = getInputData().getInt("KEY_FIRST", 0);
        int second = getInputData().getInt("KEY_SECOND", 0);
        int result = first + second; // 1 + 2 = 3
        Data output = new Data.Builder()
                .putInt("KEY_RESULT", result)
                .build();
        return Result.success(output);
    }
}

// 监听返回
WorkManager.getInstance().getWorkInfoByIdLiveData(worker.getId())
    .observe(lifecycleOwner, info -> {
         if (info != null && info.getState().isFinished()) {
           // 获取返回结果, 应该是3
           int result = info.getOutputData().getInt(KEY_RESULT, 0));
         }
    });

一些需要注意的地方

  • WorkManager 虽然在设计的时候是为了在 App 没运行的时候也能运行 Worker, 但是目前从 Google Issue Tracker 上的信息来看, 以下几种情况杀掉后任务的存活情况是这样的:

    1. 从任务管理器(最近使用)关掉: 原生的 Android 上 Worker 仍然会运行, 但是在 某些把这种操作当做强制停止的厂商一些中国厂商 的机型上, Worker 要等到下次打开 App 才会运行.
    2. 重启手机 (Worker 运行中的状态): 重启后 Worker 会继续运行.
    3. App 信息 -> 强制关闭: Worker 会再下次打开 App 的时候运行.
    4. 重启手机 (App 被强制关闭了): Worker 会再下次打开 App 的时候运行.

以上所述就是小编给大家介绍的《WorkManager-Guide&Tips》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Impractical Python Projects

Impractical Python Projects

Lee Vaughan / No Starch Press / 2018-11 / USD 29.95

Impractical Python Projects picks up where the complete beginner books leave off, expanding on existing concepts and introducing new tools that you’ll use every day. And to keep things interesting, ea......一起来看看 《Impractical Python Projects》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具