内容简介:注意一下, 同样是后台线程,如果对更好用的 WorkManager 感兴趣, 就可以继续往下看了. 大概的介绍顺序是:
WorkManager
为了方便运行一些 不着急的
、 异步的
的 后台
任务而诞生. 大部分情况下, 只需要定义好自己想做的任务, 交给 WorkManager
去执行, 剩下就不用管了.
注意一下, 同样是后台线程, WorkManager
的重点在于保证 就算 App 关掉之后后台任务也能够被执行
. 而那种可以随着 App 退出而关闭的后台任务, 还是更适合使用 ThreadPools
.
以前的实现方案
-
Service: 这是最常见的需要后台运行的方案了. 对比来说, Service 有以下几个问题:
- 可能会由于开发者的设置而疯狂运行, 这会导致手机电量被疯狂消耗. 对比之下 WorkManager 的同一个周期任务的最小间隔时间是15分钟.
-
targetSdkVersion
为 26 及以上的时候, 在不被允许创建后台服务的情况下,startService()
会抛出IllegalStateException
. 对比之下 WorkManager 会按照设定选择合适的时间运行.
-
JobScheduler: 这个最关键的就是只有 Android 5.0 以上才能使用, 其实 WorkManager 在 5.0 以上也是用这个实现的.
- AlarmManager + BroadcastReceiver. 这个方案也是可以的, WorkManager 在 5.0 以下也是这样实现的, 只是封装了更好用的 API .
如果对更好用的 WorkManager 感兴趣, 就可以继续往下看了. 大概的介绍顺序是:
- 导入
- 一次性任务的使用
- 周期性任务的使用
- 任务如何取消
- 给任务加上约束条件
- 多个任务以特定顺序执行
- 相同任务的重复处理策略
- 任务的输入和输出
- 一些需要注意的点
以下代码都可以在 Demo 中找到.
导入
和其他 JetPack 的组件一样, 在 project
的 build.gradle
文件中添加 google()
源:
allprojects { repositories { google() jcenter() } }
然后在 module
的 build.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 上的信息来看, 以下几种情况杀掉后任务的存活情况是这样的:- 从任务管理器(最近使用)关掉: 原生的 Android 上 Worker 仍然会运行, 但是在 某些把这种操作当做强制停止的厂商 或 一些中国厂商 的机型上, Worker 要等到下次打开 App 才会运行.
- 重启手机 (Worker 运行中的状态): 重启后 Worker 会继续运行.
- App 信息 -> 强制关闭: Worker 会再下次打开 App 的时候运行.
- 重启手机 (App 被强制关闭了): Worker 会再下次打开 App 的时候运行.
以上所述就是小编给大家介绍的《WorkManager-Guide&Tips》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图解Java多线程设计模式
[日] 结城浩 / 侯振龙、杨文轩 / 人民邮电出版社 / 2017-8 / 89.00元
本书通过具体的Java 程序,以浅显易懂的语言逐一说明了多线程和并发处理中常用的12 种设计模式。内容涉及线程的基础知识、线程的启动与终止、线程间的互斥处理与协作、线程的有效应用、线程的数量管理以及性能优化的注意事项等。此外,还介绍了一些多线程编程时容易出现的失误,以及多线程程序的阅读技巧等。在讲解过程中,不仅以图配文,理论结合实例,而且提供了运用模式解决具体问题的练习题和答案,帮助读者加深对多线......一起来看看 《图解Java多线程设计模式》 这本书的介绍吧!