热文导读 | 点击标题阅读
作者:snwr611
链接:https://juejin.im/post/5af4aa91f265da0b8d41f714
5月8号, I/O大会上又推出了两个新的Architeture Component库: Navigation与 WorkManager. 这里就先介绍一下 WorkManager.
一. WorkManager的一句话介绍
其实就是"管理一些要在后台工作的任务, -- 即使你的应用没启动也能保证任务能被执行".
1. 为何不用JobScheduler, AlarmManger来做?
: 其实这个想法很对. WorkManager在底层也是看你是什么版本来选到底是JobScheduler, AlamarManager来做.
JobScheduler是Android 5.x才有的. 而AlarmManager一直存在. 所以WorkManager在底层, 会根据你的设备情况, 选用JobScheduler, Firebase的JobDispatcher, 或是AlarmManager
2. 为啥不用AsyncTask, ThreadPool, RxJava?
: 这一点就要特别说明一下了. 这三个和WorkManager并不是替代的关系. 这三个工具, 能帮助你在应用中开后台线程干活, 但是应用一被杀或被关闭, 这些工具就干不了活了.
而WorkManager不是, 它在应用被杀, 甚至设备重启后仍能保证你安排给他的任务能得到执行.
其实Google自己也说了:"WorkManager并不是为了那种在应用内的后台线程而设计出来的. 这种需求你应该使用ThreadPool"
二. 例子例子
还是show me the code吧.
1. 导入WorkManager
app/build.gradle
中加入
[kotlin]
1implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01"
[java]
1implementation "android.arch.work:work-runtime:1.0.0-alpha01"
2. 一个定期Pull的例子
以我在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}
3. 讲解
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.
三. 进阶
1. 想拿到结果
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)
2. 总结入参/返回值
入参: WorkRequest.Builder.setInputData()
Worker类: 可以getIntpuData(), 以及setOutputData()
返回值: 由LiveData监听, 可以得到WorkStatus. 而WorkStatus就有getOutputDat()方法, 只是注意,这里说的inputData, outputDat, 都不是普通的int, string。而是Data类。
3. 如果任务执行完了, 应用却没被启动怎么办? 会强行启动应用来显示UI变化吗?
好问题. 但严格来说, 这个其实不是WorkManager的问题, 而是LiveData的问题.
LiveData自己本身就是和Activity的生命周期绑定的. 你不用说应用被杀了, 就是你退出了这个注册的Activity, 你都收不到LiveData的通知. 所以说你的应用被杀, 任务又执行完了时, 是没有UI通知的, 更不会强行启动你的启动. (这有点流氓~)
4. 任务链
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.
5. 插入任务时, 已经有相同的任务时, 怎么办?
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来通知监听者.
至于链式执行, 唯一工作队列这些特性在你有类似的需求时, 也能帮助到你.
如你有好的文章想和大家分享欢迎投稿,直接向我投递文章链接即可
最后,欢迎大家加入我们的知识星球,第二期开期起航火热进行中,已有近1000人加入学习:
欢迎大家尽早加入,这期是到2019年3月10日结束,所以越早加入越好,现入圈费用由89元提至99元,人员到1000人时将大幅涨价,所以快上车!
微信扫描或者点击上方二维码领取Android\Python\AI\Java等高级进阶资源
更多学习资料点击下面的“阅读原文”获取