新架构组件: WorkManager

5月8号, I/O大会上又推出了两个新的Architeture Component库: Navigation与WorkManager. 这里就先介绍一下WorkManager.java

一. WorkManager的一句话介绍

其实就是"管理一些要在后台工做的任务, -- 即便你的应用没启动也能保证任务能被执行".android

1. 为什么不用JobScheduler, AlarmManger来作?

: 其实这个想法很对. WorkManager在底层也是看你是什么版原本选究竟是JobScheduler, AlamarManager来作. JobScheduler是Android 5.x才有的. 而AlarmManager一直存在. 因此WorkManager在底层, 会根据你的设备状况, 选用JobScheduler, Firebase的JobDispatcher, 或是AlarmManagerbash

2. 为啥不用AsyncTask, ThreadPool, RxJava?

: 这一点就要特别说明一下了. 这三个和WorkManager并非替代的关系. 这三个工具, 能帮助你在应用中开后台线程干活, 可是应用一被杀或被关闭, 这些工具就干不了活了. 而WorkManager不是, 它在应用被杀, 甚至设备重启后仍能保证你安排给他的任务能获得执行.app

其实Google本身也说了:"WorkManager并非为了那种在应用内的后台线程而设计出来的. 这种需求你应该使用ThreadPool"ide

二. 例子例子

仍是show me the code吧.工具

1. 导入WorkManager

app/build.gradle中加入gradle

[kotlin]ui

implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01"this

[java]spa

implementation "android.arch.work:work-runtime:1.0.0-alpha01"

2. 一个按期Pull的例子

以我在2012年作过的一个项目为例, 当时我在作一个电商项目. 咱们有一个需求是要定时推给用户一些咱们推荐的单品, 可是当时集团尚未push service组件呢, 因此咱们当时为了及时上线, 选用的策略是"pull策略". 客户端定时去后台拉取, 看有没有新的推荐.

这时咱们要分两步走. 第一步是肯定要干什么活(去后台pull推荐信息); 第二步是让这个活入队列.

代码上咱们也分两步

  1. Worker是干活的主体. 它只管轮到了它时要作的工做. 无论其它的东西(如什么时候轮到它, 它的ID, ...).

这里要新建个Worker的子类, 重写它的doWork()方法.

class PullWorker : Worker() {
    override fun doWork(): WorkerResult {
        // 模拟设置页面中的"是否接受推送"是否被勾选
        val isOkay = this.inputData.getBoolean("key_accept_bg_work", false)
        if(isOkay) {
            Thread.sleep(5000) //模拟长时间工做

            val pulledResult = startPull()
            val output = Data.Builder().putString("key_pulled_result", pulledResult).build()
            outputData = output
            return WorkerResult.SUCCESS
        } else {
            return WorkerResult.FAILURE
        }
    }

    fun startPull() : String{
        return "szw [worker] pull messages from backend"
    }
}
复制代码
  1. 把Worker包装成一个WorkRequest, 并入列
    • WorkRequest就多了一些新属性: 如:
      • ID(通常是一个UUID, 以保证惟一性),
      • 什么时候执行,
      • 有没有限制(如只有在充电并连网时才执行此任务),
      • 执行链 (当某任务执行完了, 才能轮到我执行)
    • WorkManager就负责把WorkRequest入列
class PullEngine {
    fun schedulePull(){
        //java就请用PeriodicWorkRequest.Builder类
        val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS)
                .setInputData(
                    Data.Builder()
                        .putBoolean("key_accept_bg_work", true)
                        .build()
                )
                .build()
        
        WorkManager.getInstance().enqueue(pullRequest)
    }
}
复制代码

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 (这里为简便, 是收到结果后就打印一下日志).

[PullEngine.kt]
class PullEngine {
    fun schedulePull(){
        val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS).build()
        WorkManager.getInstance().enqueue(pullRequest)

        // 下面两行是新加的, 用来存任务的ID
        val pullRequestID = pullRequest.id
        MockedSp.pullId = pullRequestID.toString() // 模拟存在SharedPreference中
    }
}

复制代码
[PullActivity.kt]
class PullActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // UUID实现了Serializable接口. 也能由toString(), fromString()与String互转
        val uuid = UUID.fromString(MockedSp.pullId)
        WorkManager.getInstance().getStatusById(uuid)
                .observe(this, Observer<WorkStatus> { status ->
                    if (status != null){
                        val pulledResult = status.outputData.getString("key_pulled_result", "")
                        println("szw Activity getResultFromBackend : $pulledResult")
                    }
                })
    }
}

复制代码

注意, observe()方法是用来监听嘛. 它的参数分别是: observer(LifecycleOwner, Observer<WorkStatus>)

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. 任务链

WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
    .then(workC)
    .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来通知监听者.

至于链式执行, 惟一工做队列这些特性在你有相似的需求时, 也能帮助到你.

相关文章
相关标签/搜索