WorkManager
为了方便运行一些 不着急的
、 异步的
的 后台
任务而诞生. 大部分情况下, 只需要定义好自己想做的任务, 交给 WorkManager
去执行, 剩下就不用管了.
注意一下, 同样是后台线程, WorkManager
的重点在于保证 就算 App 关掉之后后台任务也能够被执行
. 而那种可以随着 App 退出而关闭的后台任务, 还是更适合使用 ThreadPools
.
Service: 这是最常见的需要后台运行的方案了. 对比来说, Service 有以下几个问题:
targetSdkVersion
为 26 及以上的时候, 在不被允许创建后台服务的情况下, startService()
会抛出 IllegalStateException
. 对比之下 WorkManager 会按照设定选择合适的时间运行. JobScheduler: 这个最关键的就是只有 Android 5.0 以上才能使用, 其实 WorkManager 在 5.0 以上也是用这个实现的.
如果对更好用的 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 的名字, 第二个参数就是重复时的操作, 有以下几种模式:
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 上的信息来看, 以下几种情况杀掉后任务的存活情况是这样的: