近期, I/O大会上又推出了两个新的Architeture Component库: Navigation与 WorkManager. 这里就先介绍一下 WorkManager.
其实就是"管理一些要在后台工作的任务, -- 即使你的应用没启动也能保证任务能被执行".
: 其实这个想法很对. WorkManager在底层也是看你是什么版本来选到底是JobScheduler, AlamarManager来做.
JobScheduler是Android 5.x才有的. 而AlarmManager一直存在. 所以WorkManager在底层, 会根据你的设备情况, 选用JobScheduler, Firebase的JobDispatcher, 或是AlarmManager
: 这一点就要特别说明一下了. 这三个和WorkManager并不是替代的关系. 这三个工具, 能帮助你在应用中开后台线程干活, 但是应用一被杀或被关闭, 这些工具就干不了活了.
而WorkManager不是, 它在应用被杀, 甚至设备重启后仍能保证你安排给他的任务能得到执行.
其实Google自己也说了:"WorkManager并不是为了那种在应用内的后台线程而设计出来的. 这种需求你应该使用ThreadPool"
还是show me the code吧.
app/build.gradle
中加入
implementation "android.arch.work:work-runtime:1.0.0-alpha01"
以我在2012年做过的一个项目为例, 当时我在做一个电商项目. 我们有一个需求是要定时推给用户一些我们推荐的单品, 但是当时集团还没有push service组件呢, 所以我们当时为了及时上线, 选用的策略是"pull策略". 客户端定时去后台拉取, 看有没有新的推荐.
这时我们要分两步走. 第一步是确定要干什么活(去后台pull推荐信息); 第二步是让这个活入队列.
代码上我们也分两步
Worker是干活的主体. 它只管轮到了它时要做的工作. 不管其它的东西(如何时轮到它, 它的ID, …).
这里要新建个Worker的子类, 重写它的doWork()方法.
1class PullWorker : Worker() {
2 override fun doWork(): WorkerResult {
3 // 模拟设置页面中的"是否接受推送"是否被勾选
4 val isOkay = this.inputData.getBoolean("key_accept_bg_work", false)
5 if(isOkay) {
6 Thread.sleep(5000) //模拟长时间工作
7 val pulledResult = startPull()
8 val output = Data.Builder().putString("key_pulled_result", pulledResult).build()
9 outputData = output
10 return WorkerResult.SUCCESS
11 } else {
12 return WorkerResult.FAILURE
13 }
14 }
15 fun startPull() : String{
16 return "szw [worker] pull messages from backend"
17 }
18}
把Worker包装成一个WorkRequest, 并入列
WorkRequest就多了一些新属性: 如:
ID(一般是一个UUID, 以保证唯一性),
何时执行,
有没有限制(如只有在充电并连网时才执行此任务),
执行链 (当某任务执行完了, 才能轮到我执行)
WorkManager就负责把WorkRequest入列
1class PullEngine {
2 fun schedulePull(){
3 //java就请用PeriodicWorkRequest.Builder类
4 val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS)
5 .setInputData(
6 Data.Builder()
7 .putBoolean("key_accept_bg_work", true)
8 .build()
9 )
10 .build()
11 WorkManager.getInstance().enqueue(pullRequest)
12 }
13}
1.干活的是 Worker 类. 我们一般是新建个Worker的子类, 并重写doWork()方法.
但是, doWork() 方法是没有参数的. 我们有时有参数的需求,怎么办?
这时就要用上Worker.getInputData()方法了.
2.同理, doWork()方法是返回void的. 你要是有结果想传出去, 就可以用Worker.setOutputData()
3.上面的两个方法所得到/设置的数据类型都是Data. 这个Data很类似我们Android中的Bundle, 也有putInt(key, value), getString(key, defaultValue)这样的方法.
一般Data的生成, 是用Data.Builder类. 如:
val output = Data.Builder().putInt(key, 23).build()
4.上面讲了WorkRequest其实就是入列的一个实体, 它包装了Worker在内.
但我们一般不直接使用WorkReqeust类, 多是用它的子类: OneTimeWorkRequest, 或是PeriodWorkReqeust.
因为我们的pull需求是每天都要去拉一次, 所以这里我们没有用OneTimeWorkRequest, 而是构建了一个24小时就重复干活的PeriodicWorkReqeust.
WorkManager 提供了一个接口让我们拿到结果, 这个东东就是 WorkStatus. 你可以由id得到你想要的那个任务的WorkStatus. 这个 WorkStatus 其实就是知道这任务没有完成, 有什么返回值.
因为前后台要解耦合的原因, 所以这个工作其实是由LiveData来完成的. 既然有LiveData, 那我们肯定要有一个 LifecycleOwner 了(一般是我们的 AppcompatActivity).
来看个例子. 以上面的pull例子为例, 若我们拉到了结果, 就显示一个 notification (这里为简便, 是收到结果后就打印一下日志).
1[PullEngine.kt]
2class PullEngine {
3 fun schedulePull(){
4 val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS).build()
5 WorkManager.getInstance().enqueue(pullRequest)
6 // 下面两行是新加的, 用来存任务的ID
7 val pullRequestID = pullRequest.id
8 MockedSp.pullId = pullRequestID.toString() // 模拟存在SharedPreference中
9 }
10}
1[PullActivity.kt]
2class PullActivity : AppCompatActivity() {
3 override fun onCreate(savedInstanceState: Bundle?) {
4 super.onCreate(savedInstanceState)
5 // UUID实现了Serializable接口. 也能由toString(), fromString()与String互转
6 val uuid = UUID.fromString(MockedSp.pullId)
7 WorkManager.getInstance().getStatusById(uuid)
8 .observe(this, Observer<WorkStatus> { status ->
9 if (status != null){
10 val pulledResult = status.outputData.getString("key_pulled_result", "")
11 println("szw Activity getResultFromBackend : $pulledResult")
12 }
13 })
14 }
15}
注意, observe()方法是用来监听嘛. 它的参数分别是: observer(LifecycleOwner, Observer)
入参: WorkRequest.Builder.setInputData()
Worker类: 可以getIntpuData(), 以及setOutputData()
返回值: 由LiveData监听, 可以得到WorkStatus. 而WorkStatus就有getOutputDat()方法, 只是注意,这里说的inputData, outputDat, 都不是普通的int, string。而是Data类。
好问题. 但严格来说, 这个其实不是WorkManager的问题, 而是LiveData的问题.
LiveData自己本身就是和Activity的生命周期绑定的. 你不用说应用被杀了, 就是你退出了这个注册的Activity, 你都收不到LiveData的通知. 所以说你的应用被杀, 任务又执行完了时, 是没有UI通知的, 更不会强行启动你的启动. (这有点流氓~)
1WorkManager.getInstance()
2 .beginWith(workA)
3 .then(workB)
4 .then(workC)
5 .enqueue()
这样就会按workA, workB, workC的顺序来执行. workA执行完了, 才会接着执行workB.
WorkManager甚至还能执行:
A --> B --> E
C --> D
这样的形式, 即A执行完了才执行了B, C执行完才执行D. B,D都执行完了才执行E.
WorkManager
可以用 beginUniqueWork()
来执行唯一工作队列("unique work sequence"). 若有任务有重复时, 怎么办?
这个主要是一个 ExistingWorkPolicy
类.
这个类也是 WorkManager
包中的类. 它其实是一个Enum. 其值有:
REPLACE: 用新任务来取代已经存在的任务
KEEP: 保留已经存在的任务. 忽视新任务
APPEND: 新任务入列. 新旧任务都存在于队列中.
总体来说, WorkManager
并不是要取代线程池/AsyncTask/RxJava. 反而是有点 AlarmManager 来做定时任务的意思. 即保证你给它的任务能完成, 即使你的应用都没有被打开, 或是设备重启后也能让你的任务被执行.
WorkManager
在设计上设计得比较好. 没有把 worker, 任务混为一谈, 而是把它们解耦成 Worker, WorkRequest. 这样分层就清晰多了, 也好扩展. (如以后再有一个什么 WorkRequest 的子类出来)
最后, WorkManager的入参出参设计得不错. WorkReqeust
负责放入参数, Worker处理并放置返回值, 最后WorkStaus中取出返回值, 并由LiveData来通知监听者.
至于链式执行, 唯一工作队列这些特性在你有类似的需求时, 也能帮助到你.
博客地址:http://blog.yoqi.me/?p=13531
这篇文章还没有评论