Android WorkManager Why and How

Android WorkManager

WorkManager应用场景

因为可以在app重启和设备重启等场景下继续保持工作状态, 所以WorkManager用来处理这种persistent的background work是比较好的选择.

当然它还可以用来设置重复任务, 也是比较方便.

可以想象的几种应用场景:

  • 定期发送网络请求, 上报状态, 失败重试.
  • 后台下载任务, 在失败后, 间隔一段时间自动重试.

优势和劣势

优势

WorkManager的几大好处:

  • 可以跨越进程死亡, 关机重启等过程, 继续未完成的工作.
  • constraints限制, 可以等条件满足时再做实际的工作, 有网络连接, wifi连接等限制. 并且条件不满足会自动cancel, 等条件满足的时候再次自动开始.
  • 可以链式组合不同的worker, 形成chain, 一个做完再接着做另一个, 如果前一个失败了就不再做下一个.
  • 重试. worker的retry状态是帮我们在失败的场景下, 自动进行下一次重试的有效手段, 可以配置重试的时间间隔是线性分布还是指数分布.

劣势

各个Android版本的实现不同, 会有一些限制条件不同.

Worker里不能运行时间太长的任务

如果执行的任务时间太久, 超时以后会被自动取消.

Android API不同版本下, worker最大的执行时间不同:

  • api=21 -> 1min
  • 23<=api<=30 -> 10min
  • api>=32 -> >10min

如果真的想要执行超过10分钟的任务, 可以在worker里调用setForeground方法, 这样会需要显示一个状态栏的小icon.

具体来源在这个文档里: developer.android.com/reference/a...

sql 复制代码
In Android version Build.VERSION_CODES.LOLLIPOP, jobs had a maximum execution time of one minute. 
Starting with Android version Build.VERSION_CODES.M and ending with Android version Build.VERSION_CODES.R, jobs had a maximum execution time of 10 minutes. 
Starting from Android version Build.VERSION_CODES.S, jobs will still be stopped after 10 minutes if the system is busy or needs the resources, but if not, jobs may continue running longer than 10 minutes.

一些执行不受控制的情况

会有一些任务没有发生的情况是不被我们控制的. (这个也可以理解, 当我们想要一些自动的行为, 必然会交出一些控制.)

也不能说是劣势吧, 有可能是故意这么设计的, 因为有一些是针对电池电量的优化.

几个Work没有running的情况:

  • 在doze mode下可能会被delay.
  • battery saver mode下也不跑任务.
  • Android只允许同一时间跑一些数量的jobs.
  • WorkManager会被Configuration中给的ThreadPool限制.
  • force stop app也会停止所有的jobs和alarms. 下次重启app, 会reschedule.

WorkManager的使用

根据使用的需求, 一般需要确定:

  • 是否需要工作条件, 设置Constraints, 可以是联网, 正在充电等条件. 不满足条件的时候会等待, 满足条件的时候会自动触发任务.
  • 是否需要多个任务chain起来. 这种一般适用于后面的工作需要基于前一任务的处理结果的情况.
  • 是否是周期性工作. 如果是, 需要用workManager.enqueueUniquePeriodicWork.
  • 同一个工作, 需要注意重复的触发条件下如何处理吗?
    • 不需要, 都是互不干涉的独立的一次性工作. 可以直接workManager.enqueue(workRequest)
    • 需要考虑任务替换或者保留(避免资源浪费), 这里就要认真考虑ExistingWorkPolicy(how)和uniqueWorkName(who)的设计.
      • 这里尤其需要注意periodic work的policy设置, 不然可能会有触发过多的问题.

sample代码, 对于简单的来说可以直接enqueue:

kotlin 复制代码
val workRequest: WorkRequest =
    OneTimeWorkRequestBuilder<SimpleWorkerWithDependency>()
        .build()
workManager.enqueue(workRequest)

可以加上Input, Constraints和ExistingPolicy等设置:

kotlin 复制代码
val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
        val workRequest = OneTimeWorkRequestBuilder<WorkerWhenNetwork>()
            .setInputData(
                workDataOf(
                    "url" to "http://helloworld"
                )
            )
            .setConstraints(constraints)
            .build()

        workManager
            .enqueueUniqueWork(
                "WorkerWhenNetwork",
                ExistingWorkPolicy.REPLACE,
                workRequest
            )

如果需要chain, 则是begin之后, 最后enqueue.

ExistingWorkPolicy

ExistingWorkPolicy用于告诉系统, 如果worker已经存在该怎么办, 旧的任务是取消还是保留.

注意这里Replace: 认uniqueWorkName, 不认实现, 即uniqueWorkName相同的不同worker也可以互相取代.

uniqueWorkName需要根据需求设计, 几种情况:

  • 字符串常量: 全局唯一的任务.
  • 利用某些唯一id: 和某个实体强相关的任务, 可以用上该实体的id.
  • 字符串常量+唯一id: 当id用在多种任务里时, 可以拼接字符串以区分不同的任务.

Constraints:

这个特性是利用系统帮我们等待一个合适的时机. 比较常用的: 有网络连接:

kotlin 复制代码
.setRequiredNetworkType(NetworkType.CONNECTED)

必须是wifi连接:

kotlin 复制代码
.setRequiredNetworkType(NetworkType.METERED)

Debug

可以通过IDE的app inspection看状态.

在现在版本的Android Studio里, 大概是这个位置:

也可以:

bash 复制代码
adb shell dumpsys jobscheduler

其他注意事项

  • 普通Worker可以通过executor切换线程.
  • CoroutineWorker: 用withContext切换dispatcher.
  • 还有支持RxJava的版本, 需要另外添加依赖.
  • 不要随便跨版本重命名worker, 因为数据库中存的是旧名字, 升级了新版本想执行旧worker的时候会构造不出来.

关键实现

WorkManager在不同的OS API level有不同的实现.

随着时间的推移, 我们慢慢就会只考虑最新版的实现, JobScheduler.

如何实现任务调度?

在高版本下, 主要是SystemJobScheduler, 里面用的是:

kotlin 复制代码
(JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE)

这里的实现细节没太往下看了, 就看到mJobScheduler.schedule(jobInfo);为止了.

在此之前, 可以看enqueue方法的实现:

kotlin 复制代码
    @Override
    public @NonNull Operation enqueue() {
        // Only enqueue if not already enqueued.
        if (!mEnqueued) {
            // The runnable walks the hierarchy of the continuations
            // and marks them enqueued using the markEnqueued() method, parent first.
            EnqueueRunnable runnable = new EnqueueRunnable(this);
            mWorkManagerImpl.getWorkTaskExecutor().executeOnTaskThread(runnable);
            mOperation = runnable.getOperation();
        } else {
            Logger.get().warning(TAG,
                    "Already enqueued work ids (" + TextUtils.join(", ", mIds) + ")");
        }
        return mOperation;
    }

GreedyScheduler是个很有趣的类.

更多的可以去相关具体源码看看:

如何实现persistent work?

用数据库做状态存储, 用的是Room.

有很多的migration代码.

比较关键的表应该是WorkSpec.

如何实现的Constraints特性?

针对具体的每一种constraint类型:

  • 有自己的ConstraintTracker类型, 比如BatteryChargingTracker, 一般实现是靠broadcast receiver来实现状态变化的通知. 只有NetworkStateTracker24不是靠广播, 是靠一个系统的callback.
  • 有自己的ConstraintController类型, 比如BatteryChargingController, 用来给tracker加listener.
  • WorkConstraintsTracker 持有所有的controllers, 有一个关键的方法是listen. 它又被scheduler和commandHandler所持有.

还有一个ConstraintTrackingWorker是为了两个约束的兼容性考虑做的代理, 具体可以看方法tryDelegateConstrainedWorkSpec的注释.

和App Startup的关系?

从setup的过程中可以看出workmanager和app startup应该有一定的关系.

这和它的初始化有关.

自动初始化

如果WorkManager全是默认配置, 那么可以用自动初始化.

kotlin 复制代码
WorkManagerInitializer implements Initializer<WorkManager>

这里用了Initializer接口, 其本质实现是一个ContentProvider.

xml 复制代码
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge" >
    <meta-data
        android:name="androidx.work.WorkManagerInitializer"
        android:value="androidx.startup" />
</provider>

发现只要添加了work-runtime的依赖, merged manifest里就会出现好多东西.

On-Demand Initialization

另一种初始化方式是On-Demand Initialization. 如果用了Hilt做依赖注入似乎只能用这个办法.

可以延迟初始化的时间, 实现lazy, 并且可以按照自己的需求配置.

这种方式需要disable WorkManagerInitializer, 通过在manifest里面删除它.

优势是可以优化启动时间, 但是这样可能会导致一个小问题: 如果app被force stop了, 那么重启之后, 所有的work都要在WorkManager.getInstance之后才会被重新安排上(rescheduled).

和其他实现的对比

关于background的任务实现如何选型, 官方这里有个文档:

developer.android.com/develop/bac...

一些简单的分类原则:

  • 如果app变不可见的时候, 任务需要停止吗? 如果需要停止, 就是asynchronous work. 这里可以对应普通的网络请求, 后台刷新等, 如果应用退出, 确实没有什么必要继续进行了.
  • 任务允许被打断吗? 如果不允许, 考虑用foreground service.
  • 有没有特定的alternative api存在? developer.android.com/develop/bac...
  • 如果以上都不满足, 那大概率要选WorkManager了.

这里官网文档链接里有图, 搬过来如下:

Reference

相关推荐
帅次6 小时前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
IAM四十二2 天前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
Wgllss4 天前
那些大厂架构师是怎样封装网络请求的?
android·架构·android jetpack
x02421 天前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton23 天前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss24 天前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack
上官阳阳1 个月前
使用Compose创造有趣的动画:使用Compose共享元素
android·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-15.属性变化控制Service
android·android studio·android jetpack
IAM四十二1 个月前
Android Jetpack Core
android·android studio·android jetpack
王能1 个月前
Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
android·ios·kotlin·web·android jetpack·kmp·kmm