WorkManager IN Jetpack

WorkManager是Android Jetpack的架构组件(Architecture component)之一,官方对它的定义是用来管理Android后台作业。

简单上手

1,添加依赖

1
2
3
4
5
6
7
8
9
// 在app的build.gradle里添加
//A: Kotlin + coroutines
implementation 'androidx.work:work-runtime-ktx:2.1.0'

//B: Java only
implementation 'androidx.work:work-runtime:2.1.0'

//C: optional - RxJava2 support
implementation 'androidx.work:work-rxjava2:2.1.0'

2,实现一个Worker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 相当于一个Task
class CountNumWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
private val count = inputData.getInt(COUNT, 0)
private val name = inputData.getString(NAME) ?: "bob"
override fun doWork(): Result {
var sum = 0
for (i in 0..count) {
sum += i
sleep()
Log.d(TAG, "-----$name count $count || sum $sum")
}
return Result.success()
}

private fun sleep() {
val time = Math.random() * 400 + 100
Log.d(CountNumWorker.TAG, "----$time")
try {
Thread.sleep(time.toLong(), 0)
} catch (e: InterruptedException) {
Log.e(CountNumWorker.TAG, "----$e")
}
}

companion object {
const val TAG = "CountNumWorker"
const val COUNT = "count"
const val NAME = "name"
}
}

3,创建一个WorkRequest

1
2
// 定义怎么运行Worker,下面这个worker将只运行一次,对应的还有周期运行的PeriodicWorkRequestBuilder
val workRequest = OneTimeWorkRequestBuilder<CountNumWorker2>().build()

4,执行Worker

1
2
// 调用WorkManager单例开始worker
WorkManager.getInstance().enqueue(workRequest)

WorkerManager作为JetPack一员,官方强烈安利搭配LiveData and ViewModel一起食用。不过单独用也完全OK,而且上手是很简单的。执行后台任务我们已经有AsyncTask,ThreadPools,RxJava…那WorkManager需要足够理由来说服大家上车。

WorkManager是啥?

WorkManager可以用来轻松安排非立即执行的异步任务,即使程序退出或者手机重启

关键功能

  • 支持API 14以上机型,在API 14-22使用BroadcastReceiver + AlarmManager,23+则是JobScheduler
  • 给work添加触发条件,比如特定网络状态和或电池多少以上才执行
  • 可以单次或周期的执行异步任务
  • 监视和管理任务
  • 按组按序执行任务
  • 可以确保任务执行,即使app退出或手机重启,叼
  • 电池友好,即使休眠模式也能工作

使用场景

  • 发送logs或analytics给后端
  • 周期性和后端同步app数据

什么时候用

img

敲黑板:是否需要立即执行的前提很重要,比如用户希望立即运行不被打断,那WorkManager就表示做不到了,这时需要用foreground service。其他情况下首选WorkManager,它具有更高的兼容性和更优雅的封装性。

进阶使用

1,传递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//IN: requestBuilder 往worker塞数据
workRequestBuilder.setInputData(getCountData("bob",10))

//IN: Data里面有个Map接收数据
private fun getCountData(name: String, count: Int): Data {
val builder = Data.Builder()
CountNumWorker.TAG
builder.putInt(CountNumWorker.COUNT, count)
builder.putString(CountNumWorker.NAME, name)
return builder.build()
}

//OUT:worker里面取数据
private val count = inputData.getInt(COUNT, 0)
private val name = inputData.getString(NAME) ?: "bob"

2,监听worker执行状态

1
2
3
4
5
6
7
8
9
10
11
12
// 实例化一个workRequestBuilder
val workRequestBuilder = OneTimeWorkRequestBuilder<CountNumWorker2>()
// 设置一下TAG
workRequestBuilder.addTag("bob")
// 监听特定TAG的worker的执行状态
workerManager.getWorkInfosByTagLiveData("bob").observe(this, Observer { workInfo ->
if (workInfo != null && workInfo.first().state == WorkInfo.State.SUCCEEDED) {
UtilToast.show("----success!")
}
})

// PS:worker可以有三种方式标示:name,id,tag。tag可以在workRequestBuilder上设置,表示一个组的worker,name和id都是在workRequestBuilder.build()后可以获取,表示单个worker,通过WorkContinuation可以设置。

3,串并联worker,S队形B队形随意排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 *            A
* |
* +----------+
* | |
* Bob lucy
* | |
* +----------+
* |
* B
// A任务先执行,然后bob和lucy一起跑,跑完了再跑B worker
val workRequestBuilder = OneTimeWorkRequestBuilder<CountNumWorker2>()
val workRequestBuilder2 = OneTimeWorkRequestBuilder<CountNumWorker2>()
val workRequestBuilderA = OneTimeWorkRequestBuilder<CountNumWorker>()
val workRequestBuilderB = OneTimeWorkRequestBuilder<CountNumWorker>()
workRequestBuilder.setInputData(getCountData("bob",10))
workRequestBuilder2.setInputData(getCountData("lucy",15))
workRequestBuilderA.setInputData(getCountData("A", 15))
workRequestBuilderB.setInputData(getCountData("B", 15))
val list = listOf(workRequestBuilder.build(), workRequestBuilder2.build())
workerManager.beginWith(workRequestBuilderA.build()).then(list).then(workRequestBuilderB.build()).enqueue()

* A C
* | |
* B D
* | |
* +-------+
* |
* E
// 左右两边任务同时跑起来,文体两开花安排上
WorkContinuation left = workManager.beginWith(A).then(B);
WorkContinuation right = workManager.beginWith(C).then(D);
WorkContinuation final = WorkContinuation.combine(Arrays.asList(left, right)).then(E);
final.enqueue();

4,Cancelling and stopping work

1
2
3
WorkManager.cancelWorkById(workRequest.id)
workerManager.cancelAllWorkByTag("bob")
workerManager.cancelUniqueWork("lucy")

5,周期任务

1
2
3
4
5
6
7
8
9
10
11
12
// 充电时worker才工作
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.build()
// 设置request
val saveRequest =
PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
// 安排上
WorkManager.getInstance()
.enqueue(saveRequest)

6,WorkManager的线程模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 简单来说就是三种,本文最开头的添加依赖里标了A,B,C
// 如果依赖B,那只能使用基础的Worker
class DownloadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): ListenableWorker.Result {
for (i in 0..99) {
try {
downloadSynchronously("https://www.google.com")
} catch (e: IOException) {
return ListenableWorker.Result.failure()
}
}
return ListenableWorker.Result.success()
}
}
// Worker默认是会创建一个Executor,但也可以自定义
WorkManager.initialize(
context,
Configuration.Builder()
.setExecutor(Executors.newFixedThreadPool(8))
.build())

// 如果依赖A(推荐),那可以使用Worker和CoroutineWorker,协程美滋滋
class CoroutineDownloadWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override val coroutineContext = Dispatchers.IO
override suspend fun doWork(): Result = coroutineScope {
val jobs = (0 until 100).map {
async {
downloadSynchronously("https://www.google.com")
}
}
// awaitAll will throw an exception if a download fails, which CoroutineWorker will treat as a failure
jobs.awaitAll()
Result.success()
}
}

// RxJava2重度患者,在A或B的基础上加个C,就可以多个RxWoker使用
public class RxDownloadWorker extends RxWorker {
public RxDownloadWorker(Context context, WorkerParameters params) {
super(context, params);
}

@Override
public Single<Result> createWork() {
return Observable.range(0, 100)
.flatMap { download("https://www.google.com") }
.toList()
.map { Result.success() };
}
}

参考

https://developer.android.com/topic/libraries/architecture/workmanager

https://codelabs.developers.google.com/codelabs/android-workmanager-kt/#0

https://medium.com/androiddevelopers/introducing-workmanager-2083bcfc4712