WorkManager组件解析

WorkManager组件解析WorkManager作为Jetpack组件的一员,其能很好的胜任应用后台逻辑处理工作。本文对WorkaManager进行较为详细的解析,包括WorkManager使用等内容

一、为什么使用WorkManager

如果工作始终要通过应用重启和系统重新启动来调度,则称之为永久性的工作。大多数应用都有在后台执行任务的需求,即通过持久性工作能够完成。Android为后台任务提供了多种解决方案,例如JobSchedulerLoaderService等,但是这些Api若是使用不当,则可能导致耗电等系列问题。

WorkManager是适用于持久性工作的推荐解决方案,其可以处理三种类型的永久性工作:

  1. 立即执行:必须立即开始且很快就完成的任务,可以加急
  2. 长时间运行:运行时间可能较长(有可能超过 10 分钟)的任务。
  3. 可延期执行:延期开始并且可以定期运行的预定任务。

WorkManager组件解析

WorkManager最低能兼容API Level 14,并且不需要你的设备安装有Google Play Services。

WorkManager能依据设备的情况,选择不同的执行方案。在API Level 23+,通过JobScheduler来完成任务;而在API Level 23以下的设备中,通过AlarmManagerBroadcast Receivers组合完成任务。但无论采用哪种方案,任务最终都是交由Executor来完成。

WorkManager组件解析

WorkManager无缝集成了CoroutinesRxJava,可以很方便插入异步API。WorkManager的一系列特性使其适用于需要可靠运行的工作,即使用户导航离开屏幕、退出应用或重启设备也不影响工作执行,例如:

  • 向后端服务发送日志或分析数据
  • 定期将应用数据与服务器同步
  • 退出应用后还应继续执行的未完成任务

WorkManager 不适用于那些可在应用进程结束时安全终止的进程内后台工作

二、基本使用

使用WorkManager前,先将相关库导入项目中

dependencies {
    def work_version = "xxx"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional - GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"

    // optional - Multiprocess support
    implementation "androidx.work:work-multiprocess:$work_version"
}

WorkManager的使用流程主要为:

  1. 创建一个后台任务Worker
  2. 定义WorkRequest,配置运行任务的方式和时间
  3. 将任务提交给系统处理
  4. 观察Worker的进度或状态

2.1 核心类

Worker

Work定义工作,即指定需要执行的任务,可以继承抽象的Worker类。doWork() 方法在 WorkManager 提供的后台线程上异步运行任务,在其中实现具体逻辑

class UploadWorker(appContext: Context, workerParams: WorkerParameters):
       Worker(appContext, workerParams) {
   override fun doWork(): Result {
       //......
       //@return 任务的执行情况,成功,失败,还是需要重新执行
       return Result.success()
   }
}

根据doWork()返回值判断任务执行情况:

  • Worker.Result.SUCCESS:任务执行成功。
  • Worker.Result.FAILURE:任务执行失败。
  • Worker.Result.RETRY:任务失败需要重新执行。需要配合WorkRequest.Builder里面的setBackoffCriteria()函数使用

WorkRequest

定义完工作,需要使用WorkManager服务进行调度才能使得工作执行。WorkRequest及其子类则定义了工作运行方式和时间,例如指定任务应该运行的环境,任务的输入参数,任务只有在有网的情况下执行等等。

WorkRequest是抽象类,其有两个子类:

  1. OneTimeWorkRequest(任务只执行一遍)
  2. PeriodicWorkRequest(任务周期性的执行)

其中WorkRequest.Builder为创建WorkRequest对象的帮助类;Constraints为指定任务运行的限制条件;Constraint.Builder来创建Constraints

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<UploadWorker>()
       .build()

WorkManager

用于排队和管理工作请求,将WorkRequest 对象传递到WorkManager的任务队列

WorkManager
    .getInstance(myContext)
    .enqueue(uploadWorkRequest)

WorkInfo

工作在整个生命周期内会经历一系列State更改,而WorkInfo用于包含其特定的任务信息与状态。

工作状态分为:ENQUEUEDRUNNINGSUCCEEDEDFAILEDBLOCKEDCANCELLED

  • 一次性工作的状态

    对于一次性工作请求,初始状态为ENQUEUED

    ENQUEUED状态下,工作满足Constraints和初始延迟计时要求后立即运行,工作状态转为RUNNING状态,然后根据结果转为SUCCEEDEDFAILED。如果结果是retry,重回到ENQUEUED状态。在此过程中可随时取消工作,状态变更为CANCELLED

    WorkManager组件解析

    SUCCEEDEDFAILEDCANCELLED 均表示此工作的终止状态。处于此状态时,WorkInfo.State.isFinished()返回true

  • 定期工作的状态

    成功和失败状态仅适用于一次性工作和链式工作,定期工作只有一个终止状态 CANCELLED

    WorkManager组件解析

  • 链式工作的状态

    关于链式工作,其多了个BLOCKED状态。关于链式工作状态,可直接参看链接和工作状态

可以使用LiveDataFuture保存WorkInfo对象,监听任务状态。

public abstract @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id);

public abstract @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id);

public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData( @NonNull String tag);
......

2.2 WorkRequest定义

先定义一个Worker,继承Worker类,实现doWork()方法,编写需要在任务中执行的代码

class TestWorker(context:Context,workerParameters: WorkerParameters):Worker(context,workerParameters) {
    
    override fun doWork(): Result {
        Log.d("TestWorker","doWork ${System.currentTimeMillis()}")
        return Result.success()
    }
}

工作是通过WorkRequestWorkManager中进行定义和配置才能运行

任务触发约束配置

可以通过WorkRequest设置任务触发条件,如设备处于充电、网络已连接等环境才触发任务

var constraints: Constraints = Constraints.Builder()
            .setRequiresCharging(true)
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()
var workRequest = OneTimeWorkRequestBuilder<TestWorker>().setConstraints(constraints).build()

关于WorkManager的约束如下所示

NetworkType 约束运行工作所需的网络类型。例如 Wi-Fi (UNMETERED)。
BatteryNotLow 如果设置为 true,那么当设备处于“电量不足模式”时,工作不会运行。
RequiresCharging 如果设置为 true,那么工作只能在设备充电时运行。
DeviceIdle 如果设置为 true,则要求用户的设备必须处于空闲状态,才能运行工作。在运行批量操作时,此约束会非常有用;若是不用此约束,批量操作可能会降低用户设备上正在积极运行的其他应用的性能。
StorageNotLow 如果设置为 true,那么当用户设备上的存储空间不足时,工作不会运行。

如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。

任务调度周期配置

WorkRequest是抽象类,其有两个子类:OneTimeWorkRequest(任务只执行一遍)与PeriodicWorkRequest(任务周期性的执行)

调度一次性工作:

如果无需额外配置,可使用静态方法from

val workRequest = OneTimeWorkRequest.from(TestWorker::class.java)

对于复杂的工作,则可以使用构建器

val workRequest = OneTimeWorkRequestBuilder<TestWorker>()
//...... 添加额外配置
.build()

调度定期工作:

有时可能需要定期运行某些工作,例如定期备份数据,定期上传日志等,可用PeriodicWorkRequest

//工作运行时间间隔设置为1小时
val workRequest = PeriodicWorkRequestBuilder<TestWorker>(1,TimeUnit.HOURS).build()

时间间隔定义为两次重复执行之间的最短时间,可以定义的最短重复间隔是 15 分钟。

如果需要运行的工作对运行时间敏感,可以将PeriodicWorkRequest配置为在每个时间间隔的灵活时间段内运行

WorkManager组件解析

灵活时间段从 repeatInterval - flexInterval 开始,一直到间隔结束

//在每小时的最后 15 分钟内运行的定期工作
val workRequest = PeriodicWorkRequestBuilder<TestWorker>(1,TimeUnit.HOURS,15,TimeUnit.MINUTES).build()

其中间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,而灵活间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS

延迟工作配置

如果工作没有约束,或者当工作加入队列时所有约束都得到了满足,那么系统可能会选择立即运行该工作。如果您不希望工作立即运行,可以将工作指定为在经过一段最短初始延迟时间后再启动。

可以通过setInitialDelay()方法,延后任务的执行

var workRequest = OneTimeWorkRequestBuilder<TestWorker>().setConstraints(constraints)
            .setInitialDelay(10,TimeUnit.MINUTES)//符合触发条件后,延迟10 minutes执行
            .build()

定期工作只有首次运行时会延迟。

工作重试与退避配置

假如Worker线程的执行出现了异常,比如服务器宕机,你可能希望过一段时间,重试该任务,那么你可以在WorkerdoWork()方法中返回Result.retry(),系统会有默认的指数退避策略来帮你重试任务

  • 退避延迟时间指定了首次尝试后重试工作前的最短等待时间。此值不能超过 10 秒(或 MIN_BACKOFF_MILLIS)。
  • 退避政策定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长。WorkManager 支持 2 个退避政策,即 LINEAREXPONENTIAL

默认政策是EXPONENTIAL,延迟时间为10S

workRequest = OneTimeWorkRequestBuilder<TestWorker>()
            .setBackoffCriteria(BackoffPolicy.LINEAR,OneTimeWorkRequest.MIN_BACKOFF_MILLIS,TimeUnit.MILLISECONDS)
            .build()

示例中,最短退避延迟时间设置为允许的最小值,即 10 秒。由于政策为 LINEAR,每次尝试重试时,重试间隔都会增加约 10 秒。例如,第一次运行以 Result.retry() 结束并在 10 秒后重试;然后,如果工作在后续尝试后继续返回 Result.retry(),那么接下来会在 20 秒、30 秒、40 秒后重试,以此类推。如果退避政策设置为 EXPONENTIAL,那么重试时长序列将接近 20、40、80 秒,以此类推。

工作标记配置

每个工作请求都有一个唯一标识符,该标识符可用于在以后标识该工作,以便取消工作或观察其进度

如果有一组在逻辑上相关的工作,对这些工作项进行标记可能也会很有帮助。通过标记,您一起处理一组工作请求。

例如,WorkManager.cancelAllWorkByTag(String) 会取消带有特定标记的所有工作请求,WorkManager.getWorkInfosByTag(String) 会返回一个 WorkInfo 对象列表,该列表可用于确定当前工作状态。

var workRequest = OneTimeWorkRequestBuilder<TestWorker>()
            .addTag("者文静斋")
            .build()

可以向单个工作请求添加多个标记。这些标记在内部以一组字符串的形式进行存储。您可以使用 WorkInfo.getTags() 获取与 WorkRequest 关联的标记集。

Worker 类中,您可以通过 ListenableWorker.getTags() 检索其标记集。

数据交互配置

在实际开发业务中,执行后台任务的时候,都会传递参数。WorkManagerWorker之间的参数传递。数据的传递通过Data对象来完成。Worker 类可通过调用 Worker.getInputData() 访问输入参数

class TestWorker(context:Context,workerParameters: WorkerParameters):Worker(context,workerParameters) {

    override fun doWork(): Result {
        val message = inputData.getString("个人公众号")
        Log.d("TestWorker","doWork $message ${System.currentTimeMillis()}")
        return Result.success()
    }
}
var workRequest = OneTimeWorkRequestBuilder<TestWorker>()
            .setInputData(workDataOf("者文公众号" to "者文静斋"))
            .addTag("者文静斋")
            .build()

同样,可以使用Data类输出返回值。可以在任务执行完成后,向WorkManager传递数据,即Result.success(outputData)

class TestWorker(context:Context,workerParameters: WorkerParameters):Worker(context,workerParameters) {

    override fun doWork(): Result {
        val message = inputData.getString("个人公众号")
        Log.d("TestWorker","doWork $message ${System.currentTimeMillis()}")
        val output: Data = workDataOf("output" to "do work success")
        return Result.success(output)
    }
}

WorkManager通过LiveDataWorkInfo.getOutputData(),得到Worker传递过来的数据

注意Data只能用于传递一些小的基本类型数据,且数据最大不能超过10kb

加急工作

WorkManager 2.7.0 引入了加急工作的概念,系统必须先为加急作业分配应用执行时间,然后才能运行作业。执行时间并非无限制,而是受配额限制。如果您的应用使用其执行时间并达到分配的配额,在配额刷新之前,您无法再执行加急工作。

可以调用 setExpedited() 来声明 WorkRequest 应该使用加急作业,以尽可能快的速度运行。

var workRequest = OneTimeWorkRequestBuilder<TestWorker>()
            .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
            .build()

配额不足时:

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST:导致作业作为普通工作请求运行
  • OutOfQuotaPolicy.DROP_WORK_REQUEST:配额不足时取消请求

以 Android 12 为目标平台的应用在后台运行时无法再启动前台服务,但一些特殊情况除外。如果应用在后台运行时尝试启动前台服务,并且前台服务不符合任何特殊情况,则系统会抛出 ForegroundServiceStartNotAllowedException

对于Android12之后,可以使用WorkManager的加急作业替代;对于Android 12之前,WorkManager会在其平台上运行前台服务。

2.3 管理工作

定义完WorkerWorkRequest,需要将工作加入队列。将工作加入队列的最简单方法是调用 WorkManager enqueue() 方法,然后传递要运行的 WorkRequest

......
WorkManager.getInstance(this).enqueue(workRequest)

唯一工作

将工作加入队列时需小心谨慎,避免重复。例如,应用可能会每 24 小时尝试将其日志上传到后端服务。如果不谨慎,即使作业只需运行一次,您最终也可能会多次将同一作业加入队列。为了实现此目标,可以将工作调度为唯一工作

唯一工作可确保同一时刻只有一个具有特定名称的工作实例。唯一名称由开发者指定,仅与一个工作实例相关联。

唯一工作可用于一次性工作,也可用于定期工作。创建方法为

参数 含义
uniqueWorkName 唯一标识工作请求的String
existingWorkPolicy enum 可告知 WorkManager:如果已有使用该名称且尚未完成的唯一工作链,应执行什么操作
work 要调度的WorkRequest

调度唯一工作时,您必须告知 WorkManager 在发生冲突时要执行的操作,其处理冲突的策略选项有4个:

  • REPLACE:用新工作替换现有工作。此选项将取消现有工作

  • KEEP:保留现有工作,并忽略新工作。

  • APPEND:将新工作附加到现有工作的末尾。此政策将导致您的新工作链接到现有工作,在现有工作完成后运行。

    现有工作将成为新工作的先决条件。如果现有工作变为 CANCELLEDFAILED 状态,新工作也会变为 CANCELLEDFAILED。如果您希望无论现有工作的状态如何都运行新工作,请改用 APPEND_OR_REPLACE

  • APPEND_OR_REPLACE 函数类似于 APPEND,不过它并不依赖于先决条件工作状态。即使现有工作变为 CANCELLEDFAILED 状态,新工作仍会运行

val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

上述代码在 sendLogs 作业已处于队列中的情况下运行,系统会保留现有的作业,并且不会添加新的作业。

观察任务状态

任务提交给系统后,通过WorkInfo获知任务状态。WorkInfo包含了任务的idtag以及Worker对象传递过来的outputData,以及任务当前的状态。

// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

该查询会返回 WorkInfo 对象的 ListenableFuture,该值包含工作的 id、其标记、其当前的 State 以及通过 Result.success(outputData) 设置的任何输出数据。

利用每个方法的 LiveData 变种,您可以通过注册监听器来观察 WorkInfo 的变化。

//在某项工作成功完成后向用户显示消息
workManager.getWorkInfoByIdLiveData(syncWorker.id)
               .observe(viewLifecycleOwner) { workInfo ->
   if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
       Snackbar.make(requireView(),
      R.string.work_completed, Snackbar.LENGTH_SHORT)
           .show()
   }
}

WorkManager 2.4.0 及更高版本支持使用 WorkQuery 对象对已加入队列的作业进行复杂查询。WorkQuery 支持按工作的标记、状态和唯一工作名称的组合进行查询。

val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

WorkQuery 中的每个组件(标记、状态或名称)与其他组件都是 AND 逻辑关系。组件中的每个值都是 OR 逻辑关系。WorkQuery 也适用于等效的 LiveData 方法 getWorkInfosLiveData()

取消和停止任务

取消任务

若不再需要运行先前加入队列的工作,可按照工作的 nameid 或与其关联的 tag 取消工作。

// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")

如果工作已经完成,系统不会执行任何操作。否则,工作的状态会更改为 CANCELLED,之后就不会运行这个工作。

停止任务

正在运行的 Worker 可能会由于以下几种原因而停止运行:

  • 明确要求取消它
  • 如果是唯一工作,明确地将 ExistingWorkPolicyREPLACE 的新 WorkRequest 加入到了队列中。旧的 WorkRequest 会立即被视为已取消。
  • 工作约束条件已不再满足
  • 系统出于某种原因指示您的应用停止工作。如果超过 10 分钟的执行期限,可能会发生这种情况。该工作会调度为在稍后重试。

上述情况,工作器会停止。可通过onStopped()回调或isStopped()属性了解工作器何时停止,进行相关资源释放操作

  1. onStopped()回调

    工作器停止后,WorkManager 会立即调用 ListenableWorker.onStopped()。替换此方法可关闭可能保留的所有资源。

  2. isStopped()属性

    可以调用 ListenableWorker.isStopped() 方法以检查工作器是否已停止。若工作器执行长时间运行操作或重复操作,应经常检查此属性,用作停止工作的信号。

任务进度更新与观察

WorkManager 2.3.0-alpha01 为设置和观察工作器的中间进度添加了一流的支持。

ListenableWorker支持setProgressAsync()API,允许保留中间进度。开发者能可通过界面观察到的中间进度。进度由 Data 类型表示够设置

更新进度

Kotlin 中,您可以使用 CoroutineWorker 对象的 setProgress() 扩展函数来更新进度信息。

class ProgressWorker(context: Context, parameters: WorkerParameters) :
    CoroutineWorker(context, parameters) {

    companion object {
        const val Progress = "Progress"
        private const val delayDuration = 1L
    }

    override suspend fun doWork(): Result {
        val firstUpdate = workDataOf(Progress to 0)
        val lastUpdate = workDataOf(Progress to 100)
        setProgress(firstUpdate)
        delay(delayDuration)
        setProgress(lastUpdate)
        return Result.success()
    }
}

观察进度

可以使用 getWorkInfoBy…()getWorkInfoBy…LiveData() 方法,并引用 WorkInfo

WorkManager.getInstance(applicationContext)
    // requestId is the WorkRequest id
    .getWorkInfoByIdLiveData(requestId)
    .observe(observer, Observer { workInfo: WorkInfo? ->
            if (workInfo != null) {
                val progress = workInfo.progress
                val value = progress.getInt(Progress, 0)
                // Do something with progress information
            }
    })

2.4 任务链

可以使用 WorkManager 创建工作链并将其加入队列。工作链用于指定多个依存任务并定义这些任务的运行顺序,尤其是需要以特定顺序运行多个任务时。

创建任务链可以使用WorkManager.beginWith(OneTimeWorkRequest)WorkManager.beginWith(List)等方法

WorkManager.getInstance(this)
    .beginWith(workA)
    .then(workB)   
    .then(workC)
    .enqueue()

任务会按照设置的顺序依次执行A、B、C。WorkManager在执行过程中,遇到其中一个Work不成功,会停止执行。比如代码执行到WorkB返回FAILURE状态,代码结束,WorkC不执行

WorkManager.getInstance(this)
.beginWith(listof(plantName1, plantName2, plantName3) // 三个对象将并行作业
.then(cache)// 执行完3个plantName后,再执行cache
.then(upload) //...
.enqueue()

输入合并器

链接 OneTimeWorkRequest 实例时,父级工作请求的输出将作为子级的输入传入。上面的示例中,plantName1plantName2plantName3 的输出将作为 cache 请求的输入传入。

WorkManger使用InputMerger管理多个父级工作请求输入。

WorkManager 提供两种不同类型的 InputMerger

OverwritingInputMerger 是默认的合并方法。如果合并过程中存在键冲突,键的最新值将覆盖生成的输出数据中的所有先前版本。

例如,如果每种植物的输入都有一个与其各自变量名称("plantName1""plantName2""plantName3")匹配的键,传递给 cache 工作器的数据将具有三个键值对。

WorkManager组件解析

如果存在冲突,那么最后一个工作器将在争用中“取胜”,其值将传递给 cache

WorkManager组件解析

假设我们要保留所有植物名称工作器的输出,则应使用 ArrayCreatingInputMerger

val cache: OneTimeWorkRequest = OneTimeWorkRequestBuilder<PlantWorker>()
   .setInputMerger(ArrayCreatingInputMerger::class)
   .setConstraints(constraints)
   .build()

ArrayCreatingInputMerger 将每个键与数组配对。如果每个键都是唯一的,您会得到一系列一元数组

WorkManager组件解析

如果存在任何键冲突,所有对应的值会分组到一个数组中

WorkManager组件解析

2.5 自定义WorkManager

默认情况下,应用启动时,WorkManager 使用适合大多数应用的合理选项自动进行配置。如果需要进一步控制 WorkManager 管理和调度工作的方式,可以通过自行初始化 WorkManager 来自定义 WorkManager 配置。

按需初始化

通过按需初始化,可以在仅需要WorkManager时创建该组件,不用每次应用启动都创建,提高应用启动性能。

如需提供自己的配置,必须先移除默认初始化程序,使用合并规则 tools:node="remove" 更新 AndroidManifest.xml

WorkManager 2.6 开始,应用启动功能在WorkManager内部使用。若需提供自定义初始化程序,需移除androidx.startup节点

 <!-- 如果您不在应用中使用应用启动功能,则可以将其彻底移除 -->
 <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" tools:node="remove">
 </provider>
<!-- 仅移除 WorkManagerInitializer 节点 --> 
<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge">
    <!-- If you are using androidx.startup to initialize other components -->
    <meta-data android:name="androidx.work.WorkManagerInitializer" android:value="androidx.startup" tools:node="remove" />
 </provider>

实现Configguration.Provider

让您的 Application 类实现 Configuration.Provider 接口,并提供您自己的 Configuration.Provider.getWorkManagerConfiguration() 实现。

class App : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration() =
        Configuration.Builder()
            .setWorkerFactory(RenameWorkerFactory())
            .setMinimumLoggingLevel(Log.VERBOSE)
            .build()
}

如需查看可用自定义的完整列表,请参阅 Configuration.Builder() 参考文档

2.6 自定义WorkerFactory

使用WorkManager时,需要定义一个Worker的子类。在某个时刻,WorkManager会通过WorkerFactory反射实例化你定义的Worker。默认的WorkerFactory创建Worker需要两个参数:

  1. Application’s Context
  2. WorkerParameters

实际开发中,可能需要为Worker构造函数添加其他参数,满足我们的功能需求,例如需要引用Retrofit服务跟远程服务器进行通讯,这时就需要自定义WorkerFactory

创建自定义WorkerFactory的步骤其实就是在自定义WorkManager步骤基础上加上对自定义的WorkerFactory的设置

class MyWorkerFactory(private val service: UpvoteStoryHttpApi) : WorkerFactory() {

    override fun createWorker( appContext: Context, workerClassName: String, workerParameters: WorkerParameters ): ListenableWorker? {
        return UpvoteStoryWorker(appContext, workerParameters, DesignerNewsService)

    }
}
class MyApplication : Application(), Configuration.Provider {
    override fun getWorkManagerConfiguration(): Configuration = 
        Configuration.Builder()
                     .setMinimumLoggingLevel(android.util.Log.DEBUG)
                     .setWorkerFactory(MyWorkerFactory(DesignerNewsService))
                     .build()
...
}

DelegatingWorkerFactory使用

实际开发中可能会有多个Worker类,示例中直接返回某个特定Worker实例的操作就不是很适用,可以通过DelegatingWorkerFactory解决。

可以使用DelegatingWorkerFactory,将其设置到我们的WorkerFactory中,从而支持多个多个工厂。这种情况下,如果一个WorkerFactory没有创建WorkerDelegatingFactory会去找到下一个WorkerFactory

class MyWorkerFactory(private val service: DesignerNewsService) : WorkerFactory() {

    override fun createWorker( appContext: Context, workerClassName: String, workerParameters: WorkerParameters ): ListenableWorker? {

        return when(workerClassName) {
            UpvoteStoryWorker::class.java.name ->
                ConferenceDataWorker(appContext, workerParameters, service)
            else ->
                // 返回 null,这样基类就可以代理到默认的 WorkerFactory
                null
        }

    }
}

上述情况,工厂会检查是否知道如何处理作为参数传入的workerClassName,如果不知道,就返回null,而 DelegatingWorkerFactory 便会去寻找下一个注册的工厂。如果没有任何被注册的工厂知道如何处理某个类,那么它将回退到使用反射的默认工厂。

同时我们需要修改一下前述的WorkManager配置

class MyApplication : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration(): Configuration {
        val myWorkerFactory = DelegatingWorkingFactory()
     myWorkerFactory.addFactory(MyWorkerFactory(service))
     // 在这里添加您应用中可能会使用的其他 WorkerFactory

            return Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.INFO)
                    .setWorkerFactory(myWorkerFactory)
                    .build()
    }
...
}

2.7 线程处理

WorkManager 提供了四种不同类型的工作基元:

  1. Worker

    最简单的实现,WorkManager 会在后台线程中自动运行该基元(您可以将它替换掉)

  2. CoroutineWorker

    Kotlin用户建议实现。CoroutineWorker 实例公开了后台工作的一个挂起函数。默认情况下,这些实例运行默认的 Dispatcher,也可以自行自定义

  3. RxWorker

    RxJava建议实现。如果很多异步代码是用RxJava 建模的,则应使用 RxWorker。与所有 RxJava 概念一样,可以自由选择所需的线程处理策略

  4. ListenableWorker

    WorkerCoroutineWorkerRxWorker 的基类,专为需要与基于回调的异步 API(例如 FusedLocationProviderClient)进行交互并且不使用 RxJavaJava 开发者而设计

Worker中线程处理

使用 Worker 时,WorkManager 会自动在后台线程中调用 Worker.doWork()。该后台线程来自WorkManagerConfiguration 中指定的 Executor。你可以通过自定义的方式修改Executor

WorkManager.initialize(
    context,
    Configuration.Builder()
         // Uses a fixed thread pool of size 8 threads.
        .setExecutor(Executors.newFixedThreadPool(8))
        .build())

Worker.doWork()是同步调用,应以阻塞方式完成整个后台工作,并在方法退出时完成工作。如果在doWork()中调用异步API并返回Result,回调可能无法正常允许,这种情况,考虑使用ListenableWorker

class DownloadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): ListenableWorker.Result {
        repeat(100) {
            if (isStopped) {
                break
            }

            try {
                downloadSynchronously("https://www.google.com")
            } catch (e: IOException) {
                return ListenableWorker.Result.failure()
            }

        }

        return ListenableWorker.Result.success()
    }
}

CoroutineWorker中线程处理

WorkManager协程提供了一流的支持。CoroutineWorker中包含了doWork的挂起版本。

class CoroutineDownloadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val data = downloadSynchronously("https://www.google.com")
        saveData(data)
        return Result.success()
    }
}

CoroutineWorker.doWork() 是一个“挂起”函数。此代码不同于 Worker,不会在 Configuration 中指定的 Executor 中运行,而是默认为 Dispatchers.Default。您可以提供自己的 CoroutineContext 来自定义这个行为。

class CoroutineDownloadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        withContext(Dispatchers.IO) {
            val data = downloadSynchronously("https://www.google.com")
            saveData(data)
            return Result.success()
        }
    }
}

CoroutineWorker 通过取消协程并传播取消信号来自动处理停工情况。

可以使用 RemoteCoroutineWorkerListenableWorker 的实现)将工作器绑定到特定进程。详细操作可参看WorkManagerMultiProcessSample

ListenableWorker中线程处理

如果需要处理基于回调的异步操作,这种情况无法依靠Worker来完成,因为器无法以阻塞方式完成工作,这时就需要ListenableWorker

抽象方法 ListenableWorker.startWork() 会返回一个将使用操作的 Result 设置的 ListenableFuture,其是一个Future,用于提供附加监听器和传播异常的功能。

class CallbackWorker(
        context: Context,
        params: WorkerParameters
) : ListenableWorker(context, params) {
    override fun startWork(): ListenableFuture<Result> {
        return CallbackToFutureAdapter.getFuture { completer ->
            val callback = object : Callback {
                var successes = 0

                override fun onFailure(call: Call, e: IOException) {
                    completer.setException(e)
                }

                override fun onResponse(call: Call, response: Response) {
                    ++successes
                    if (successes == 100) {
                        completer.set(Result.success())
                    }
                }
            }
                                                  //添加取消监听器
 completer.addCancellationListener(cancelDownloadsRunnable, executor)

            repeat(100) {
                downloadAsynchronously("https://example.com", callback)
            }

            callback
        }
    }
}

三、原理解析

关于WorkManager原理解析,基于2.7.1版本

关于WorkMannager的原理机制,可以参看下图:

WorkManager组件解析

WorkManager的原理,简单总结为:

  1. WorkManager的初始化是在app冷启动后,包含了ConfigurationWorkManagerTaskExecutorWorkDatabaseSchedulersProcessor等的初始化过程
  2. Schedulers有两个,其中GreedyScheduler执行没有任何约束的非周期性的任务;SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler:执行周期性或者有约束性的任务
  3. 对于非约束条件,执行enqueue后,调用到WorkContinuationImplenqueue方法,其将持有的EnqueueRunnable对象将任务添加到db,并交给Schedulers去调度,交由GreedyScheduler处理
  4. GreedyScheduler经过一系列判断后,调用WorkManagerstartWork()方法,将持有的StartWorkRunnable对象会将任务交给Processor去处理,执行startWork()方法。
  5. Processor创建一个WorkerWrapper对象,由它去调用WorkerstartWork()方法,执行我们自定义worker的任务,并返回相应的result。之后对db进行更新,执行下一个任务。
  6. 对于有约束条件的周期性任务,是根据build version交由SystemJobSchedulerSystemAlarmScheduler进行处理。

3.1 WorkManager初始化

关于WorkManager的初始化在自定义WorkManager部分其实已经点明了。从 WorkManager 2.6 开始,应用启动功能在WorkManager内部使用。

直接看一下WorkManagerInitializer


/** * Initializes {@link androidx.work.WorkManager} using {@code androidx.startup}. */
public final class WorkManagerInitializer implements Initializer<WorkManager> {
    @NonNull
    @Override
    public WorkManager create(@NonNull Context context) {
        // Initialize WorkManager with the default configuration.
        Logger.get().debug(TAG, "Initializing WorkManager with default configuration.");
        WorkManager.initialize(context, new Configuration.Builder().build());
        return WorkManager.getInstance(context);
    }
    ......
}

WorkManager是个单例,初始化之前会创建一个默认的Configuration,设置管理和调度工作的方式。看一下initialize()方法

public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
    WorkManagerImpl.initialize(context, configuration);
}

WorkManager是个抽象类,真正构造方法在子类WorkManagerImpl实现

/** * Initializes the singleton instance of {@link WorkManagerImpl}. You should only do this if * you want to use a custom {@link Configuration} object and have disabled * WorkManagerInitializer. * * @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
    synchronized (sLock) {
        if (sDelegatedInstance != null && sDefaultInstance != null) {
            throw new IllegalStateException("//......");
        }

        if (sDelegatedInstance == null) {
            context = context.getApplicationContext();
            if (sDefaultInstance == null) {
                sDefaultInstance = new WorkManagerImpl(
                    context,
                    configuration,
                    new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
            }
            sDelegatedInstance = sDefaultInstance;
        }
    }
}

initialize()方法初始化一个WorkManagerImpl对象,并传入一个WorkManagerTaskExecutor对象,用来执行WorkManager的任务。

WorkManagerTaskExecutor可自行点击源码查看,其创建了一个 SerialExecutor 对象,里面初始化了一个ArrayDeque双端队列,用于保证后期创建的任务顺序执行。其中ExecutorConfiguration 中创建的固定大小线程池。

跟踪上述WorkManagerImpl的构造函数,最后走到下面这个构造函数

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public WorkManagerImpl( @NonNull Context context, @NonNull Configuration configuration, @NonNull TaskExecutor workTaskExecutor, @NonNull WorkDatabase database) {
    Context applicationContext = context.getApplicationContext();
    Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
    List<Scheduler> schedulers =
        createSchedulers(applicationContext, configuration, workTaskExecutor);
    Processor processor = new Processor(
        context,
        configuration,
        workTaskExecutor,
        database,
        schedulers);
    internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}

WorkManagerImpl构造函数主要内容:

  1. 完成创建数据库操作,使用Room框架
  2. createSchedulers创建调度者集合
  3. 创建Processor类,管理Schedulers的执行

Scheduler创建

看一下createSchedulers()方法实现

public List<Scheduler> createSchedulers( @NonNull Context context, @NonNull Configuration configuration, @NonNull TaskExecutor taskExecutor) {

    return Arrays.asList(
        Schedulers.createBestAvailableBackgroundScheduler(context, this),
        new GreedyScheduler(context, configuration, taskExecutor, this));
}

createSchedulers()返回一个Scheduler数组,其中createBestAvailableBackgroundScheduler()创建一个最有效的后台调度者

static Scheduler createBestAvailableBackgroundScheduler( @NonNull Context context, @NonNull WorkManagerImpl workManager) {

    Scheduler scheduler;

    if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
        scheduler = new SystemJobScheduler(context, workManager);
        setComponentEnabled(context, SystemJobService.class, true);
        Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
    } else {
        scheduler = tryCreateGcmBasedScheduler(context);
        if (scheduler == null) {
            scheduler = new SystemAlarmScheduler(context);
            setComponentEnabled(context, SystemAlarmService.class, true);
            Logger.get().debug(TAG, "Created SystemAlarmScheduler");
        }
    }
    return scheduler;
}

createBestAvailableBackgroundScheduler()方法主要内容:

  1. 如果Android版本号>=23,返回SystemJobScheduler,使用JobScheduler完成调度
  2. 如果手机支持GCM,则返回GcmScheduler调度者
  3. 其他情况返回SystemAlarmScheduler,内部使用AlarmManager实现原理

internalInit实现

回到WorkManagerImpl构造函数,看一下internalInit实现

private void internalInit(@NonNull Context context, @NonNull Configuration configuration, @NonNull TaskExecutor workTaskExecutor, @NonNull WorkDatabase workDatabase, @NonNull List<Scheduler> schedulers, @NonNull Processor processor) {

    //......
    mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
}
public class ForceStopRunnable implements Runnable {
    @Override
    public void run() {
        //......
        try {
            forceStopRunnable();
            break;
        }
        //......
    }

    public void forceStopRunnable() {
        boolean needsScheduling = cleanUp();
        if (shouldRescheduleWorkers()) {
            Logger.get().debug(TAG, "Rescheduling Workers.");
            mWorkManager.rescheduleEligibleWork();
            // Mark the jobs as migrated.
            mWorkManager.getPreferenceUtils().setNeedsReschedule(false);
        } else if (isForceStopped()) {
            Logger.get().debug(TAG, "Application was force-stopped, rescheduling.");
            mWorkManager.rescheduleEligibleWork();
        } else if (needsScheduling) {
            Logger.get().debug(TAG, "Found unfinished work, scheduling it.");
            Schedulers.schedule(
                mWorkManager.getConfiguration(),
                mWorkManager.getWorkDatabase(),
                mWorkManager.getSchedulers());
        }
    }

}

ForceStopRunnable的主要作用是在WorkManager初始化过程中,发现了未完成的,需要重新执行的任务,或者app被强制kill的情况下,直接对Scheduler进行调度

WorkManager初始化小结

  1. WorkManager的初始化是在app冷启动后,由WorkManagerInitializer这个ContentProvider执行的。

  2. 初始化过程包含了ConfigurationWorkManagerTaskExecutorWorkDatabaseSchedulersProcessor等的初始化过程。

  3. Schedulers有两个

    GreedyScheduler:执行没有任何约束的非周期性的任务

    SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler:执行周期性或者有约束性的任务

  4. 初始化的最后,会根据情况找到需要被执行的任务进行调度执行。

3.2 WorkRequest创建

关于WorkRequest创建内容较为简单,这里不详细介绍,自行查看源码。

WorkRequest的创建是为了持有三个重要的成员变

  1. mId:由UUID生成的任务id。
  2. mWorkSpec:每个任务的属性。
  3. mTags:每个任务的标签。

3.3 非约束条件任务执行

介绍WorkManager使用时,定义完WorkerWorkerRequest,需要将工作提交到任务队列中

WorkManager.getInstance(this).enqueue(workRequest)

WorkManager的实际实现类是WorkManagerImpl,看一下内部的enqueue()实现,最终调用WorkContinuationImpl实例的enqueue方法

@Override
@NonNull
public Operation enqueue( @NonNull List<? extends WorkRequest> requests) {
	//......
    return new WorkContinuationImpl(this, requests).enqueue();
}

WorkContinuationImplWorkContinuation的子类,该类保存了任务相关的所有信息,如WorkManagerWorkRequest等。

看一下WorkContinuationImplenqueue实现

@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().executeOnBackgroundThread(runnable);
        mOperation = runnable.getOperation();
    } else {
        Logger.get().warning(TAG,
                             String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
    }
    return mOperation;
}

enqueue()方法主要内容:

  1. 创建EnqueueRunnable
  2. 获取WorkManagerTaskExecutor对象
  3. 通过之前在Configuration创建的线程池中执行EnqueueRunnable任务。

任务分配

看一下EnqueueRunnable实现

public class EnqueueRunnable implements Runnable {
    //......
    @Override
    public void run() {
        try {
            if (mWorkContinuation.hasCycles()) {
                throw new IllegalStateException(
                    String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
            }
            //任务持久化保存到数据库中
            boolean needsScheduling = addToDatabase();
            if (needsScheduling) {
                // Enable RescheduleReceiver, only when there are Worker's that need scheduling.
                final Context context =
                    mWorkContinuation.getWorkManagerImpl().getApplicationContext();
                PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
                //调度任务执行
                scheduleWorkInBackground();
            }
            mOperation.setState(Operation.SUCCESS);
        } catch (Throwable exception) {
            mOperation.setState(new Operation.State.FAILURE(exception));
        }
    }

    @VisibleForTesting
    public void scheduleWorkInBackground() {
        WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
        Schedulers.schedule(
            workManager.getConfiguration(),
            workManager.getWorkDatabase(),
            workManager.getSchedulers());
    }
}

run方法先会调用addToDatabase 根据策略不同将任务添加到数据库或者移除,然后调用 PackageManagerHelperRescheduleReceiver 注册到 manifest.xml 内。

EnqueueRunnable类 在scheduleWorkInBackground方法中调度任务执行,内部调用Schedulers类的schedule方法,分配任务。

public static void schedule( @NonNull Configuration configuration, @NonNull WorkDatabase workDatabase, List<Scheduler> schedulers) {
    if (schedulers == null || schedulers.size() == 0) {
        return;
    }
    WorkSpecDao workSpecDao = workDatabase.workSpecDao();
    List<WorkSpec> eligibleWorkSpecsForLimitedSlots;
    List<WorkSpec> allEligibleWorkSpecs;
    //获取到需要执行的任务数据
    workDatabase.beginTransaction();
    try {
        eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(
            configuration.getMaxSchedulerLimit());
        //......
    }finally {
        workDatabase.endTransaction();
    }
    //......
    for (Scheduler scheduler : schedulers) {
        if (scheduler.hasLimitedSchedulingSlots()) {
            scheduler.schedule(eligibleWorkSpecsArray);
        }
    }
    //......
    for (Scheduler scheduler : schedulers) {
        if (!scheduler.hasLimitedSchedulingSlots()) {
            scheduler.schedule(eligibleWorkSpecsArray);
        }
    }
}

这里关注没有约束条件任务的执行,看一下GreedySchedulerschedule()实现

@Override
public void schedule(@NonNull WorkSpec... workSpecs) {
    //......
    for (WorkSpec workSpec : workSpecs) {
        long nextRunTime = workSpec.calculateNextRunTime();
        long now = System.currentTimeMillis();
        //WorkSpec是ENQUEUED的状态
        if (workSpec.state == WorkInfo.State.ENQUEUED) {
            //延迟任务
            if (now < nextRunTime) {
                // Future work
                if (mDelayedWorkTracker != null) {
                    mDelayedWorkTracker.schedule(workSpec);
                }
            } else if (workSpec.hasConstraints()) {	//存在约束条件
                if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
                    // Ignore requests that have an idle mode constraint.
                    Logger.get().debug(TAG,
                                       String.format("Ignoring WorkSpec %s, Requires device idle.",
                                                     workSpec));
                } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                    // Ignore requests that have content uri triggers.
                    Logger.get().debug(TAG,
                                       String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
                                                     workSpec));
                } else {
                    constrainedWorkSpecs.add(workSpec);
                    constrainedWorkSpecIds.add(workSpec.id);
                }
            } else {
                Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
                //执行任务
                mWorkManagerImpl.startWork(workSpec.id);
            }
        }
    }
    // onExecuted() which is called on the main thread also modifies the list of mConstrained
    // WorkSpecs. Therefore we need to lock here.
    synchronized (mLock) {
        if (!constrainedWorkSpecs.isEmpty()) {
            Logger.get().debug(TAG, String.format("Starting tracking for [%s]",
                                                  TextUtils.join(",", constrainedWorkSpecIds)));
            mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
            mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
        }
    }
}

GreedySchedulerschedule方法在判断任务满足没有约束条件等要求后,直接调用startWork执行任务。

WorkManagerImpl.startWork最终调用到WorkManagerTaskExecutorexecuteOnBackgroundThread方法执行StartWorkRunnable

public void startWork( @NonNull String workSpecId, @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
    mWorkTaskExecutor
        .executeOnBackgroundThread(
        new StartWorkRunnable(this, workSpecId, runtimeExtras));
}
public class StartWorkRunnable implements Runnable {
    //......
    public void run() {
        mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
    }
}

getProcessor()方法获取的是创建WorkManager时创建的Processor对象。看一下该方法实现

public boolean startWork( @NonNull String id, @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {

    WorkerWrapper workWrapper;
    synchronized (mLock) {
        //......

        //数据封装成 WorkerWrapper
        workWrapper =
            new WorkerWrapper.Builder(
            mAppContext,
            mConfiguration,
            mWorkTaskExecutor,
            this,
            mWorkDatabase,
            id)
            .withSchedulers(mSchedulers)
            .withRuntimeExtras(runtimeExtras)
            .build();
        ListenableFuture<Boolean> future = workWrapper.getFuture();
        future.addListener(
            new FutureListener(this, id, future),
            mWorkTaskExecutor.getMainThreadExecutor());
        mEnqueuedWorkMap.put(id, workWrapper);
    }
    mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
    Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
    return true;
}

ProcessorstartWork方法将数据包装为WorkerWrapper,然后再次调用WorkManagerTaskExecutor类执行WorkerWrapper任务

WorkerWrapper继承自Runnable,看一下其实现

@WorkerThread
@Override
public void run() {
    mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
    mWorkDescription = createWorkDescription(mTags);
    runWorker();
}
private void runWorker() {
    //......
    //创建一个WorkerParameters对象
    final WorkerParameters params = new WorkerParameters(
        UUID.fromString(mWorkSpecId),
        input,
        mTags,
        mRuntimeExtras,
        mWorkSpec.runAttemptCount,
        mConfiguration.getExecutor(),
        mWorkTaskExecutor,
        mConfiguration.getWorkerFactory(),
        new WorkProgressUpdater(mWorkDatabase, mWorkTaskExecutor),
        new WorkForegroundUpdater(mWorkDatabase, mForegroundProcessor, mWorkTaskExecutor));
    //创建Worker对象
    if (mWorker == null) {
        mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
            mAppContext,
            mWorkSpec.workerClassName,
            params);
    }

    //......
    final ListenableFuture<Void> runExpedited = foregroundRunnable.getFuture();
    runExpedited.addListener(new Runnable() {
        @Override
        public void run() {
            try {
                runExpedited.get();
                Logger.get().debug(TAG,
                                   String.format("Starting work for %s", mWorkSpec.workerClassName));
                // Call mWorker.startWork() on the main thread.
                mInnerFuture = mWorker.startWork();
                future.setFuture(mInnerFuture);
            } catch (Throwable e) {
                future.setException(e);
            }
        }
    }, mWorkTaskExecutor.getMainThreadExecutor());
    //......
}
//Worker.java
@Override
public final @NonNull ListenableFuture<Result> startWork() {
    mFuture = SettableFuture.create();
    getBackgroundExecutor().execute(new Runnable() {
        @Override
        public void run() {
            try {
                Result result = doWork();
                mFuture.set(result);
            } catch (Throwable throwable) {
                mFuture.setException(throwable);
            }

        }
    });
    return mFuture;
}

WorkerWrapper的主要内容:

  1. 通过反射机制获取到ListenableWorker对象
  2. 调用ListenableWorker.startWork,实际上是调用Worker类的startWork方法
  3. Worker类的startWork方法中又会调用doWork方法,也就是我们复写的doWork方法。

非约束条件任务执行小结

  1. WorkManager执行了enqueue()后,创建WorkContinuationImpl对象执行enqueue()方法。
  2. WorkContinuationImpl持有的EnqueueRunnable对象将任务添加到db,并交给Schedulers去调度。
  3. Schedulers将任务交给每一个Scheduler去处理,对于非约束条件的一次性任务由GreedyScheduler处理
  4. GreedyScheduler经过一系列判断后,调用WorkManagerstartWork()方法执行这种一次性,非延迟,无约束的任务。
  5. WorkManager持有的StartWorkRunnable对象会将任务交给Processor去处理,执行startWork()方法。
  6. Processor创建一个WorkerWrapper对象,由它去调用WorkerstartWork()方法,执行我们自定义worker的任务,并返回相应的result
  7. 任务完成后,WorkerWrapper会根据result对任务状态,db等进行更新,然后schedule下一个任务。

3.4 带约束条件任务执行

对于带约束条件的任务,是根据build version交由SystemJobSchedulerSystemAlarmScheduler进行处理。

这里仅看一下SystemJobScheduler

SystemJobScheduler使用的是JobScheduler来调度执行任务。通常JobScheduler的使用步骤如下:

  1. 创建JobService
  2. 配置JobInfo
  3. 执行。
public SystemJobScheduler(@NonNull Context context, @NonNull WorkManagerImpl workManager) {
    this(context,
         workManager,
         (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE),//获取到了系统服务JobScheduler进行重载
         new SystemJobInfoConverter(context));//用来将我们传递的数据转换为JobInfo类型
}
public SystemJobScheduler( Context context, WorkManagerImpl workManager, JobScheduler jobScheduler, SystemJobInfoConverter systemJobInfoConverter) {
    mContext = context;
    mWorkManager = workManager;
    mJobScheduler = jobScheduler;
    mSystemJobInfoConverter = systemJobInfoConverter;
}

直接看一下SystemJobSchedulerschedule()实现

@Override
public void schedule(@NonNull WorkSpec... workSpecs) {
    WorkDatabase workDatabase = mWorkManager.getWorkDatabase();
    IdGenerator idGenerator = new IdGenerator(workDatabase);

    //......
    WorkSpec currentDbWorkSpec = workDatabase.workSpecDao().getWorkSpec(workSpec.id);
    //......
    //获取对应的系统编号
    SystemIdInfo info = workDatabase.systemIdInfoDao()
        .getSystemIdInfo(workSpec.id);
    //......
    //如果info为空,通过jobId与workSpec.id生成新的数据插入SystemIdInfo 
    if (info == null) {
        SystemIdInfo newSystemIdInfo = new SystemIdInfo(workSpec.id, jobId);
        mWorkManager.getWorkDatabase()
            .systemIdInfoDao()
            .insertSystemIdInfo(newSystemIdInfo);
    }
    //scheduleInternal方法
    scheduleInternal(workSpec, jobId);
    //......
}

看一下scheduleInternal实现

@VisibleForTesting
public void scheduleInternal(WorkSpec workSpec, int jobId) {
    //将workspec转化为jobinfo
    JobInfo jobInfo = mSystemJobInfoConverter.convert(workSpec, jobId);
    //......
    //将jobInfo放入JobScheduler中
    int result = mJobScheduler.schedule(jobInfo);
    //......
}

JobScheduler是系统服务,由android程序启动的时候自动拉起无需手动去操作,与JobScheduler对应的jobservice由系统自动生成。

系统自动实现了一个SystemJobService,当JobScheduler中的条件满足时,SystemJobService就会被调用到。

执行任务的服务类,在onStartJob()中,会调用WorkManagerImplstartWork()执行任务

@Override
public boolean onStartJob(@NonNull JobParameters params) {
    //......
    mWorkManagerImpl.startWork(workSpecId, runtimeExtras);
    return true;
}

关于mWorkManagerImpl.startWork()过程不再赘述,与3.3部分的相关内容一致。

今天的文章WorkManager组件解析分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/19375.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注