Android WorkManager 初探

前言

总结和学习 Android WorkManager 的相关用法。

WorkManager 有什么特殊之处

在日常开发中,有些任务需要在异步线程执行,并且这些工作耗时较长,通过普通的子线程或后台服务无法胜任时,就需要 WorkManager 了。

WorkManager 适用于需要可靠运行的工作,即使用户导航离开屏幕、退出应用或重启设备也不影响工作的执行。例如:

  • 向后端服务发送日志或分析数据。
  • 定期将应用数据与服务器同步。 WorkManager 不适用于那些可在应用进程结束时安全终止的进程内后台工作。

它也并非对所有需要立即执行的工作都适用的通用解决方案

可靠运行 这个特点太有诱惑力了,尤其是对于周期性的工作。相比通过诸如1像素的 Activity、进程保活、互相保活/拉起之类的方式来执行特定的工作,使用官方提供的 WorkManager 可以避免做一些非常 hack 的工作,正所谓事半功倍。

WorkManager 的功能

相比传统的 AsyncTask/Coroutine/RxJava/Executors ,WorkManager 有更鲜明的特点。

  • 结合系统特性的约束: 可以结合设备当前的网络、电量等状况,约束任务执行的条件。比如等到用户连接 WiFi 后进行 App 的更新,电量充足的时候进行系统更新,条件不满足就等待,都可以通过 WorkManager 内置的 API 非常方便的实现。

  • 强大的调度: 通过 WorkManager 执行的工作,会由系统通过 SQLite 数据库进行管理,而上层开发者可以基于工作的标记进行状态监控、取消等操作。

  • 工作链和灵活的重试策略: 这一点熟练使用 RxJava 的同学应该非常喜欢。将多个工作通过合理的编排进行串联并联,根据工作执行的结果进行重试,WorkManager 都支持。

同时 WorkManager 可以与 Coroutine 及 RxJava 无缝衔接。

这里需要明白的是,WorkManager 不是用来替代 Coroutine 或其他可以执行异步操作框架的,而是基于这些框架的特点提供了更强大的功能,是互补的关系,一些通过 Coroutine 就可以完成的工作,就不要强行往 WorkManager 上套了,不要为了用而用,很多时候合适就足够了。

WorkManager 入门

关于使用 WorkManager 如何添加依赖及 demo 级别的的使用场景,可以直接参考 WorkManager 使用入门 ,非常简单。

从官方提供的工作类型,可以看到主要有两类工作,即一次性和周期性的,顾名思义一个是执行一次就完事,一类是周期性重复执行的工作。

下面就通过一个简单的示例,从一次性工作(在 WorkManager 中,可以多执行多个任务,规范期间一个任务被定义为一个 Work,这里索性就称为工作)开始,了解一下具体如何使用 WorkManager.

  1. 定义目标

一个删除文件的任务,连续多次调用,覆盖执行。删除任务执行时,传入的路劲参数异常或文件不存在时返回失败,正常删除则返回成功。

  1. 创建 Work
kotlin 复制代码
    class CleanWork(appContext: Context, workerParameters: WorkerParameters) :
        Worker(appContext, workerParameters) {
        override fun doWork(): Result {
            val path = inputData.getString("path")
            return if (TextUtils.isEmpty(path)) {
                Result.failure(Data.Builder().putString("result", "filepath is null").build())
            } else {
                if (cleanOutDateFile(path!!)) {
                    Result.success()
                } else {
                    Result.failure(Data.Builder().putString("result", "file not exist").build())
                }
            }
        }
    }

这里关注以下几点 :

  • 继承抽象类 Worker 定义自己的 Worker ,实现 doWork 方法。
  • 返回参数 Result 有三个类型
    • Result.success():工作成功完成。
    • Result.failure():工作失败。
    • Result.retry():工作失败,应根据其重试政策在其他时间尝试。
  • Work 入参和结果的传递
    • 通过 inputData 的 getXXX 方法,可以获取调用任务时传入的参数
    • 通过 Result 返回结果是参入的 Data 参数,可以返回具体的结果,不传的话,就相当于 void 类型的函数。

简单看一下 cleanOutDateFile 的定义

kotlin 复制代码
    private fun cleanOutDateFile(path: String): Boolean {
        val file = File(path)
        return if (file.exists()) {
            file.delete()
            true
        } else {
            false
        }
    }
  1. 创建 WorkRequest 并加入队列
kotlin 复制代码
    fun clean(context: Context, path: String) {
        val request =
            OneTimeWorkRequestBuilder<CleanWork>()
                .addTag("cleanWork")
                .setInputData(workDataOf("path" to path)).build()

        WorkManager.getInstance(context)
            .enqueueUniqueWork("clean", ExistingWorkPolicy.REPLACE, request)
    }

这里关注以下几点 :

  • OneTimeWorkRequestBuilder 的泛型参数就是上面自定义的 Work
  • 通过 addTag 可以给当前要执行的 Work 添加唯一标识,这样可以基于这个 tag 获取 Work 执行的结果,或者取消 Work
  • setInputData 传入 Data 对象,即可给 Work 传参,这里的参数类型其实就是 Map,因此可以传递各种类型的值,确保 key 和 Work 中对应即可。
  • WorkRequest 加入队列时,可以简单调用 enqueue 方法,这样如果有重复的 WorkRequest 发生冲突,默认会采取保留现有 Work ,抛弃新 WorkRequest 。或者像这里一样调用 enqueueUniqueWork,通过定义 tag ,自行制定面对冲突情况的策略。
  1. 调用并观察 Work 执行情况
kotlin 复制代码
class WorkManagerViewModel(app: Application) : AndroidViewModel(app) {
    private val workManager = WorkManager.getInstance(app)

    val cleanWorkInfo = workManager.getWorkInfosByTagLiveData("cleanWork")
}


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

        workManagerViewModel.cleanWorkInfo.observe(this@FilterActivity) {
            Log.i("cleanWork", "info -> $it")
            if (it != null && it.size > 0) {
                val work = it[it.size - 1]
                Log.i("cleanWork", "$work")
            val out = work.outputData.getString("result")
                Log.i("cleanWork", "$out")               
            }
        }


        val path = cacheDir.absolutePath + File.separator + "current.txt"
        WorkUtil.clean(it.context, path)

    }

这里连续调用两次 clean 方法之后看日志

shell 复制代码
20:23:16.391 17128-17128 cleanWork                           I  info -> [WorkInfo{mId='d64379e8-17b5-484d-9c2f-2273c1fd629f', mState=SUCCEEDED, mOutputData=Data
                                                                                                    {}, mTags=[cleanWork, com.engineer.android.mini.jetpack.work.WorkUtil$CleanWork], mProgress=Data {}}]
20:23:16.391 17128-17128 cleanWork                           I  WorkInfo{mId='d64379e8-17b5-484d-9c2f-2273c1fd629f', mState=SUCCEEDED, mOutputData=Data
                                                                                                    {}, mTags=[cleanWork, com.engineer.android.mini.jetpack.work.WorkUtil$CleanWork], mProgress=Data {}}
20:23:16.391 17128-17128 cleanWork                           I  null
20:25:07.611 17128-17128 cleanWork                           I  info -> [WorkInfo{mId='b238f68c-b1c8-4994-975b-7f5d2b67d10c', mState=FAILED, mOutputData=Data {result : file not
                                                                                                    exist, }, mTags=[cleanWork, com.engineer.android.mini.jetpack.work.WorkUtil$CleanWork], mProgress=Data {}}]
20:25:07.611 17128-17128 cleanWork                           I  WorkInfo{mId='b238f68c-b1c8-4994-975b-7f5d2b67d10c', mState=FAILED, mOutputData=Data {result : file not
                                                                                                    exist, }, mTags=[cleanWork, com.engineer.android.mini.jetpack.work.WorkUtil$CleanWork], mProgress=Data {}}
20:25:07.611 17128-17128 cleanWork                           I  file not exist

可以看到,第一次执行成功后,再次进行 clean 操作时就返回了 file not exist 的错误信息,而整个队列里也只有一条 WorkInfo,说明设置的 ExistingWorkPolicy.REPLACE 的策略也生效了。

这里通过这个简单的示例可以看到,WorkManager 非常强大,而且其 API 的定义非常友好,参数和方法命名很恰当,通过名称就可以知道要方法或参数的含义。

小结

WorkManager 最大的特点是其执行的是持久化的任务。这点我们通过 Android Studio Inspection 功能就可以看到,他通过 SQLite 数据库从不同维度存储了各种插入到队列中 Worker 的信息。

因此,在定义 Worker ,创建 WorkRequest 和选择 Worker 添加到队列中的方式是都要谨慎,因为这些信息都会被存储。同时,我们也可以通过 Inspection 工具查看当前进程中存在的 Worker 信息,进行问题的排查和了解。

参考文档

相关推荐
drebander14 分钟前
使用 Java Stream 优雅实现List 转化为Map<key,Map<key,value>>
java·python·list
乌啼霜满天24917 分钟前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn23 分钟前
java入门 自定义springboot starter
java·开发语言·spring boot
程序猿阿伟23 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
找藉口是失败者的习惯30 分钟前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Grey_fantasy33 分钟前
高级编程之结构化代码
java·spring boot·spring cloud
弗锐土豆40 分钟前
工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程
java·opencv·安全·检测·面部
Elaine20239141 分钟前
零碎04 MybatisPlus自定义模版生成代码
java·spring·mybatis
小小大侠客1 小时前
IText创建加盖公章的pdf文件并生成压缩文件
java·pdf·itext
一二小选手1 小时前
【MyBatis】全局配置文件—mybatis.xml 创建xml模板
xml·java·mybatis