Jetpack架构组件库:WorkManager

news2024/11/15 21:38:44

WorkManager

WorkManager 是Android最新一代的后台调度 API,主要是用来替换先前的旧版后台调度 API(如 JobScheduler 等)。

WorkManager 是适合用于持久性工作的推荐解决方案。如果工作始终要通过应用重启和系统重新启动来调度,便是持久性的工作。由于大多数后台处理操作都是通过持久性工作完成的,因此 WorkManager 是适用于后台处理操作的主要推荐 API。

持久性工作的类型

WorkManager 可处理三种类型的持久性工作:

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

在这里插入图片描述

图 1 大致表明了不同类型的持久性工作彼此之间的关系。

下表大致列出了各种工作类型:

类型周期使用方式
立即一次性OneTimeWorkRequest 和 Worker。如需处理加急工作,请对 OneTimeWorkRequest 调用 setExpedited()。
长期运行一次性或定期任意 WorkRequest 或 Worker。在工作器中调用 setForeground() 来处理通知。
可延期一次性或定期PeriodicWorkRequest 和 Worker。

使用 WorkManager 的优势

除了具备更为简单且一致的 API 之外,WorkManager 还具备许多其他关键优势:

  • 工作约束: 使用工作约束明确定义工作运行的最佳条件。例如,仅在设备采用不按流量计费的网络连接时、当设备处于空闲状态或者有足够的电量时运行。

  • 强大的调度: WorkManager 允许您使用灵活的调度窗口调度工作,以运行一次性或重复工作。您还可以对工作进行标记或命名,以便调度唯一的、可替换的工作以及监控或取消工作组。
    已调度的工作存储在内部托管的 SQLite 数据库中,由 WorkManager 负责确保该工作持续进行,并在设备重新启动后重新调度。
    此外,WorkManager 遵循低电耗模式等省电功能和最佳做法,因此您在这方面无需担心。

  • 加急工作: 您可以使用 WorkManager 调度需在后台立即执行的工作。您应该使用加急工作来处理对用户来说很重要且会在几分钟内完成的任务。

  • 灵活的重试政策: 有时工作会失败,WorkManager 提供了灵活的重试政策,包括可配置的指数退避政策。

  • 工作链: 对于复杂的相关工作,您可以使用直观的接口将各个工作任务串联起来,这样您便可以控制哪些部分依序运行,哪些部分并行运行。对于每项工作任务,您可以定义工作的输入和输出数据。将工作串联在一起时,WorkManager 会自动将输出数据从一个工作任务传递给下一个工作任务。

WorkManager.getInstance(myContext)
    .beginWith(workA) 
    .then(workB) 
    .then(listOf(workC1, workC2, workC3)
    .then(workC) 
    .then(if (save) workD else workE)
    .enqueue()
  • 内置线程互操作性: WorkManager 无缝集成 Coroutines 和 RxJava,让您可以插入自己的异步 API,非常灵活。
    注意:尽管 Coroutines 和 WorkManager 分别是针对于不同用例推荐的解决方案,但它们二者并不是互斥的关系。您可以在通过 WorkManager 调度的工作中使用协程。

使用 WorkManager 保证工作可靠性

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

  • 向后端服务发送日志或分析数据
  • 定期将应用数据与服务器同步。

一个最好用的场景是:当网络状态恢复时执行某个后台任务可靠性指定条件的任务,哪怕App进程被杀了,也一定会执行。

WorkManager 不适用于那些可在应用进程结束时安全终止的进程内后台工作。它也并非对所有需要立即执行的工作都适用的通用解决方案。

与其他 API 的关系

虽然协程是适合某些用例的推荐解决方案,但您不应将其用于持久性工作。请务必注意,协程是一个并发框架,而 WorkManager 是一个持久性工作库。同样,AlarmManager 仅适合用于时钟或日历。

API推荐使用场景与 WorkManager 的关系
Coroutines所有不需要持久的异步工作。协程是在 Kotlin 中退出主线程的标准方式。不过,它们在应用关闭后会释放内存。对于持久性工作,请使用 WorkManager。
AlarmManager仅限闹钟。与 WorkManager 不同,AlarmManager 会使设备从低电耗模式中唤醒。因此,它在电源和资源管理方面来讲并不高效。AlarmManager 仅适合用于精确闹钟或通知(例如日历活动)场景,而不适用于后台工作。

WorkManager 的使用

添加依赖

dependencies {
    val work_version = "2.7.1"
    // (Java only)
    // implementation("androidx.work:work-runtime:$work_version")
    // Kotlin + coroutines
    implementation("androidx.work:work-runtime-ktx:$work_version")
}

一般只需使用work-runtime-ktx版本的就可以了。

WorkManager 的使用主要包括三个核心类:

在这里插入图片描述

定义 Worker

工作使用 Worker 类定义。 Worker 类的 doWork() 方法在 WorkManager 提供的后台线程上异步运行。

如需为 WorkManager 创建一些要运行的工作,请继承 Worker 类并重写 doWork() 方法。例如,如需创建上传图像的 Worker,您可以执行以下操作:

class UploadWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {

    override fun doWork(): Result {
        // Do the work here--in this case, upload the images.
        uploadImages()
        // Indicate whether the work finished successfully with the Result
        return Result.success()
    }
}

doWork() 返回的 Result 会通知 WorkManager 服务工作是否成功,以及工作失败时是否应重试工作。

  • Result.success():工作成功完成。
  • Result.failure():工作失败。
  • Result.retry():工作失败,应根据其重试政策在其他时间尝试。

创建 WorkRequest

定义工作后,必须使用 WorkManager 服务进行调度该工作才能运行。对于如何调度工作,WorkManager 提供了很大的灵活性。您可以将其安排为在某段时间内定期运行,也可以将其安排为仅运行一次。

WorkRequest 对象包含 WorkManager 调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。Worker 负责定义工作内容,WorkRequest则负责定义工作运行方式和时间。

执行工作器的确切时间取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供最佳行为。

WorkRequest 本身是抽象基类。该类有两个派生实现:

  • OneTimeWorkRequest :适用于调度非重复性的一次性工作
  • PeriodicWorkRequest 适合调度以一定间隔重复执行的工作

调度一次性工作

对于无需额外配置的简单工作,请使用静态方法 from:

val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

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

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

最后,您需要使用 enqueue() 方法将 WorkRequest 提交到 WorkManager

WorkManager.getInstance(myContext).enqueue(uploadWorkRequest)

调度加急工作

WorkManager 2.7.0 引入了加急工作的概念。这使 WorkManager 能够执行重要工作,同时使系统能够更好地控制对资源的访问权限。

加急工作具有以下特征:

  • 重要性:加急工作适用于对用户很重要或由用户启动的任务。
  • 速度:加急工作最适合那些立即启动并在几分钟内完成的简短任务。
  • 配额:限制前台执行时间的系统级配额决定了加急作业是否可以启动。
  • 电源管理:电源管理限制(如省电模式和低电耗模式)不太可能影响加急工作。
  • 延迟时间:系统立即执行加急工作,前提是系统的当前工作负载允许执行此操作。这意味着这些工作对延迟时间较为敏感,不能安排到以后执行。

在用户想要发送消息或附加的图片时,可能会在聊天应用内使用加急工作。同样,处理付款或订阅流程的应用也可能需要使用加急工作。这是因为这些任务对用户很重要,会在后台快速执行,并需要立即开始执行。

可以通过调用 setExpedited() 来声明 WorkRequest 应该使用加急作业,以尽可能快的速度运行。例如以下代码示例:

val request = OneTimeWorkRequestBuilder()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context).enqueue(request)

在此示例中,我们初始化 OneTimeWorkRequest 的实例并对其调用 setExpedited(),然后,此请求就会变成加急工作。如果配额允许,它将立即开始在后台运行。

为了保持加急作业的向后兼容性,WorkManager 可能会在 Android 12 之前版本的平台上运行前台服务。前台服务可以向用户显示通知

Android 12 之前,工作器中的 getForegroundInfoAsync()getForegroundInfo() 方法可让 WorkManager 在您调用 setExpedited() 时显示通知。

如果您想要请求任务作为加急作业运行,则所有的 ListenableWorker 都必须实现 getForegroundInfo 方法。

注意:如果未能实现对应的 getForegroundInfo 方法,那么在旧版平台上调用 setExpedited 时,可能会导致运行时崩溃。

工作器不知道自身所执行的工作是否已加急。不过,在某些版本的 Android 上,如果 WorkRequest 被加急,工作器可以显示通知。

为此,WorkManager 提供了 getForegroundInfoAsync() 方法,您必须实现该方法,让 WorkManager 在必要时显示通知,以便启动 ForegroundService

CoroutineWorker

如果您使用 CoroutineWorker,则必须实现 getForegroundInfo()。然后,在 doWork() 内将其传递给 setForeground()。这样做会在 Android 12 之前的版本中创建通知

请参考以下示例:

class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
    CoroutineWorker(appContext, workerParams) {

    override suspend fun getForegroundInfo(): ForegroundInfo {
        return ForegroundInfo(NOTIFICATION_ID, createNotification())
    }

    override suspend fun doWork(): Result {
        try {
            setForeground(getForegroundInfo())
        } catch (e: IllegalStateException) {
            return  Result.failure()
        }
        // TODO()
    }

    private fun createNotification() : Notification {
        // TODO()
    }
}

注意:您应该将 setForeground() 封装在 try/catch 块中,以捕获可能出现的 IllegalStateException。如果您的应用此时无法在前台运行,便可能会发生这类异常。在 Android 12 及更高版本中,您可以使用更详细的 ForegroundServiceStartNotAllowedException

配额策略

系统必须先为加急作业分配应用执行时间,然后才能运行作业。执行时间并非无限制,而是受配额限制。如果您的应用使用其执行时间并达到分配的配额,在配额刷新之前,您无法再执行加急工作。这样,Android 可以更有效地在应用之间平衡资源。

每个应用均有自己的前台执行时间配额。可用的执行时间取决于待机模式存储分区和进程的重要性。

注意:当您的应用在前台运行时,配额不会限制加急工作的执行。仅当应用在后台运行或移至后台时,执行时间配额才适用。因此,您应加急想在后台继续的工作。当应用在前台运行时,您可以继续使用 setForeground()。

您可以控制当应用达到其执行配额时加急工作需要做出如何反应,可以通过 setExpedited() 设置如下值:

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST,这会导致作业作为普通工作继续请求运行。
  • OutOfQuotaPolicy.DROP_WORK_REQUEST,这会在配额不足时导致请求取消。
延迟加急工作

系统会尝试在调用指定的加急作业后,尽快执行该作业。不过,与其他类型的作业一样,如果出现以下情况下,系统可能会延迟启动新的加急工作:

  • 系统负载过高:当有过多作业已在运行或者当系统内存不足时,就会发生这种情况。
  • 已超出加急作业配额限制:加急工作使用基于应用待机存储分区的配额系统,并限制滚动时间窗口中的最大执行时间。用于加急工作的配额比用于其他类型的后台作业的配额限制性更强。

调度定期工作

您的应用有时可能需要定期运行某些工作。例如,您可能要定期备份数据、定期下载应用中的新鲜内容或者定期上传日志到服务器。

使用 PeriodicWorkRequest 创建定期执行的 WorkRequest 对象的方法如下:

val saveRequest =
		PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    		// Additional configuration
           .build()

在此示例中,工作的运行时间间隔定为一小时。

时间间隔定义为两次重复执行之间的最短时间。工作器的确切执行时间取决于您在 WorkRequest 对象中设置的约束以及系统执行的优化。(注意:可以定义的最短重复间隔是 15 分钟

注意:WorkManager调度定期工作最少15分钟执行一次重复任务,它只能保证任务一定执行,但不能保证指定的精确时间执行。

弹性运行间隔

如果您的工作的性质致使其对运行时间敏感,您可以将 PeriodicWorkRequest 配置为在每个时间间隔的弹性时间段内运行,如图 1 所示。
在这里插入图片描述
如需定义具有弹性时间段的定期工作,请在创建 PeriodicWorkRequest 时传递 flexInterval 以及 repeatInterval。弹性时间段从 repeatInterval - flexInterval 开始,一直到间隔结束。

以下是可在每小时的最后 15 分钟内运行的定期工作的示例。

val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

重复间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS(15分钟),而弹性间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS(5分钟)。

设置 Worker 的约束

您可以对定期工作设置约束。例如,您可以为工作请求添加约束,以便工作仅在用户设备充电时运行。在这种情况下,除非满足约束条件,否则即使过了定义的重复间隔,PeriodicWorkRequest 也不会运行。这可能会导致工作在某次运行时出现延迟,甚至会因在相应间隔内未满足条件而被跳过。

约束可确保将工作延迟到满足最佳条件时运行。WorkManager定义了一些类型的约束

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

如需创建一组约束并将其与某项工作相关联,请使用一个 Contraints.Builder() 创建 Constraints 实例,并将该实例分配给 WorkRequest.Builder()

例如,以下代码会构建了一个工作请求,该工作请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest = 
	OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

如果指定了多个约束,工作将仅在满足所有约束时才会运行。

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

调度延期工作

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

下面举例说明了如何将工作设置为在加入队列后至少经过 10 分钟后再运行。

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

该示例说明了如何为 OneTimeWorkRequest 设置初始延迟时间,您也可以为 PeriodicWorkRequest 设置初始延迟时间(在这种情况下,定期工作只有首次运行时会延迟。)。

注意:执行工作器的确切时间还取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供可能的最佳行为。

重试和退避策略

如果您需要让 WorkManager 重试工作,可以在 doWork() 方法返回 Result.retry()。然后,系统将根据退避延迟时间和退避策略重新调度工作。

  • 退避延迟时间 指定了首次尝试后重试工作前的最短等待时间。此值不能超过 10 秒(或 MIN_BACKOFF_MILLIS)。

  • **退避策略 ** 定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长。WorkManager 支持 2 个退避策略,即 LINEAREXPONENTIAL

每个工作请求都有退避策略和退避延迟时间。默认策略是 EXPONENTIAL,延迟时间为 10 秒,但您可以在工作请求配置中替换此设置。

以下是自定义退避延迟时间和策略的示例:

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

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

注意:退避延迟时间不精确,在两次重试之间可能会有几秒钟的差异,但绝不会低于配置中指定的初始退避延迟时间。

标记 Worker

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

如果有一组在逻辑上相关的工作,对这些工作项进行标记可能也会很有帮助。通过标记,您可以将一组工作请求一起处理。例如,WorkManager.cancelAllWorkByTag(String) 会取消带有特定标记的所有工作请求,WorkManager.getWorkInfosByTag(String) 会返回一个 WorkInfo 对象列表,该列表可用于确定当前工作状态。

要标记一个 Worker,请使用 WorkRequest 构建器的 addTag() 方法。以下代码展示了如何向工作添加“cleanup”标记:

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

最后,可以向单个工作请求添加多个标记。这些标记在内部以一组字符串的形式进行存储。您可以使用 WorkInfo.getTags() 获取与 WorkRequest 关联的标记集。在 Worker 类中,您可以通过 getTags() 方法检索其标记集。

向 Worker 传递参数

通过 WorkRequest 构建器创建 Worker 对象时,可以调用 setInputData() 方法设置需要向 Worker 对象传递的参数内容。

参数内容是以键值对Pair的形式存储在 Data 对象中, WorkManager 会在执行工作时将输入 Data 传递给Worker 对象。在 Worker 类中可通过调用 Worker.getInputData() 来获取输入参数。

例如,处理图片上传的工作可能需要使用待上传图片的 URI 作为输入数据:

class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput = inputData.getString("IMAGE_URI") ?: return Result.failure() // 根据key来查询
       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}
 
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf("IMAGE_URI" to "http://xxx")) // 支持多个 key to value 的 Pair 对
   .build()

同样,可使用 Data 类输出返回值,例如返回 Result.success(Data) 。任务的 WorkInfo 中会提供返回值:

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.id)
        .observe(this, Observer { info ->
            if (info != null && info.state.isFinished) {
                val myResult = info.outputData.getInt(KEY_RESULT, myDefaultValue)
                // ... do something with the result ...
            }
        })

如果您链接多个任务,一个任务的输出可以作为任务链中下一个任务的输入。如果是简单链(即一个 OneTimeWorkRequest 后面跟着另一个 OneTimeWorkRequest),第一个任务通过调用 Result.success(Data) 返回其结果,下一个任务通过调用 getInputData() 提取该结果。如果是更复杂的链(例如有多个任务都将输出发送给同一个后续任务),您可以在 OneTimeWorkRequest.Builder 上定义 InputMerger,以指定当多个不同任务返回具有相同键的输出时应执行什么操作。

Worker 的状态

一次性工作的状态

对于 one-time 工作请求,工作的初始状态为 ENQUEUED

ENQUEUED 状态下,您的工作会在满足其约束和初始延迟计时要求后立即运行。接下来,该工作会转为 RUNNING 状态,然后可能会根据工作的结果转为 SUCCEEDEDFAILED 状态;或者,如果结果是 retry,它可能会回到 ENQUEUED 状态。在此过程中,随时都可以取消工作,取消后工作将进入 CANCELLED 状态。

在这里插入图片描述
SUCCEEDEDFAILEDCANCELLED 均表示此工作的终止状态。如果您的工作处于上述任何状态,WorkInfo.State.isFinished() 都将返回 true

定期工作的状态

成功和失败状态仅适用于一次性工作和链式工作。定期工作只有一个终止状态 CANCELLED。这是因为定期工作永远不会结束。每次运行后,无论结果如何,系统都会重新对其进行调度。

在这里插入图片描述

管理 Worker

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

val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(context).enqueue(myWork)

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

Unique Worker

Unique Worker 是一个很实用的概念,可确保同一时刻只有一个具有特定名称的工作实例。与 ID 不同的是,Unique Worker 的名称是人类可读的,由开发者指定,而不是由 WorkManager 自动生成。与标记不同,Unique Worker 的名称仅与一个工作实例相关联。

Unique Worker既可用于一次性工作,也可用于定期工作。您可以通过调用以下方法之一创建Unique Worker序列,具体取决于您是调度重复工作还是一次性工作:

  • WorkManager.enqueueUniqueWork()(用于一次性工作)
  • WorkManager.enqueueUniquePeriodicWork()(用于定期工作)

这两种方法都接受 3 个参数:

  • uniqueWorkName - 用于唯一标识工作请求的 String 类型名称。
  • existingWorkPolicy - 此 enum 可告知 WorkManager:如果已有使用该名称且尚未完成的唯一工作链时,应执行什么策略(见下文)。
  • work - 要调度的 WorkRequest

借助Unique Worker,我们可以解决前面提到的重复调度问题。


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

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

当您需要逐步构建一个长任务链时,也可以利用唯一工作序列。例如,照片编辑应用可能允许用户撤消一长串操作。其中的每一项撤消操作可能都需要一些时间来完成,但必须按正确的顺序执行。在这种情况下,应用可以创建一个“撤消”链,并根据需要将每个撤消操作附加到该链上。

冲突解决策略

调度 Unique Worker 时,必须告知 WorkManager 在发生冲突时要执行的操作。您可以通过在将工作加入队列时传递一个枚举值来实现此目的。

对于一次性工作,可以设置 ExistingWorkPolicy,它提供了用于处理冲突的 4 个选项:

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

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

  • APPEND:将新工作附加到现有工作的末尾。此政策将导致您的新工作链接到现有工作,在现有工作完成后运行。
    现有工作将成为新工作的先决条件。如果现有工作变为 CANCELLEDFAILED 状态,新工作也会变为 CANCELLEDFAILED。如果您希望无论现有工作的状态如何都运行新工作,请改用 APPEND_OR_REPLACE

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

对于定期工作,您需要提供一个 ExistingPeriodicWorkPolicy,它支持 REPLACEKEEP 这两个选项。选项的功能与其对应的 ExistingWorkPolicy 功能相同。

观察/查询 Worker 的信息

在将Worker加入队列后,您可以随时按其 nameid 或与其关联的 tagWorkManager 中进行查询,以检查其状态。

// 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 对象的 ListenableFutureWorkInfo 对象中包含了 Worker 的 idtag标记、当前的状态 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.getWorkInfoByIdLiveData()返回的是一个LiveData对象,可以给它添加一个Observer观察者回调。

需要注意的是,由于LiveData的特性,当doWork执行完后,发送给Activity中注册的观察者回调时,会收到多次,因为每一种状态都会执行回调,只有当 isFinished=true 时,才表示任务真的执行完成了。(LiveData绑定Activity的生命周期的观察者回调,在onCreate、onResume都会回调)如果不使用LiveData,就只会有一次。

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.id).observe(this, Observer { info ->
     if (info != null && info.state.isFinished) {
          val myResult = info.outputData.getInt(KEY_RESULT, myDefaultValue)
          // ... do something with the result ...
      }
})
使用 WorkQuery 进行复杂的 Worker 查询

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

以下示例说明了如何查找带有“syncTag”标记、处于 FAILEDCANCELLED 状态、且 Unique Worker 名称为“preProcess”或“sync”的所有工作。

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 逻辑关系。
例如:(name1 OR name2 OR ... ) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)

WorkQuery 也适用于等效的 LiveData 方法 getWorkInfosLiveData()

取消和停止工作

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

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

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

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

WorkManager 会在后台检查工作的 State。如果工作已经完成,系统不会执行任何操作。否则,工作的状态会更改为 CANCELLED,之后就不会运行这个工作。任何依赖于此工作的 WorkRequest 作业也将变为 CANCELLED

目前,RUNNING 可收到对 ListenableWorker.onStopped() 的调用。如需执行任何清理操作,请覆写此方法。

注意:cancelAllWorkByTag(String) 会取消具有给定标记的所有工作。

监听 Worker 的停止

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

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

在这些情况下,您的工作器会停止。

您应该合作地取消正在进行的任何工作,并释放您的工作器保留的所有资源。例如,此时应该关闭所打开的数据库和文件句柄。有两种机制可让您获取工作器何时停止:

  • onStopped() 回调: 在您的工作器停止后,WorkManager 会立即调用 ListenableWorker.onStopped()。覆写此方法可关闭您可能保留的所有资源。

  • isStopped() 属性: 您可以调用 ListenableWorker.isStopped() 方法以检查工作器是否已停止。如果您在工作器中执行长时间运行的操作或重复操作,您应经常检查此属性,并将其用作尽快停止工作的信号。

注意:WorkManager 会忽略已收到 onStop 信号的工作器所设置的 Result,因为工作器已被视为停止。

观察 Worker 的中间进度

如果应用在前台运行时,工作器保持运行状态,那么可以使用返回 WorkInfo 的 LiveData 的 API 向用户显示此信息。

ListenableWorker 现在支持 setProgressAsync() API,此类 API 允许保留中间进度。借助这些 API,开发者能够设置可通过界面观察到的中间进度。进度由 Data 类型表示,这是一个可序列化的属性容器(类似于 inputoutput,并且受到相同的限制)。

只有在 ListenableWorker 运行时才能观察到和更新进度信息。如果尝试在 ListenableWorker 完成执行后在其中设置进度,则将会被忽略。您还可以使用 getWorkInfoByXXX()getWorkInfoByXXXLiveData() 方法来观察进度信息。这两个方法会返回 WorkInfo 的实例,后者有一个返回 Data 类型的新 getProgress() 方法。

更新进度

对于使用 ListenableWorkerWorkerJava 开发者,setProgressAsync() API 会返回 ListenableFuture<Void>;更新进度是异步过程,因为更新过程涉及将进度信息存储在数据库中。在 Kotlin 中,您可以使用 CoroutineWorker 对象的 setProgress() 扩展函数来更新进度信息。

此示例展示了一个简单的 ProgressWorkerWorker 在启动时将进度设置为 0,在完成后将进度值更新为 100

// Kotlin 版本
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import kotlinx.coroutines.delay

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()
    }
}
// Java 版本
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

public class ProgressWorker extends Worker {

    private static final String PROGRESS = "PROGRESS";
    private static final long DELAY = 1000L;

    public ProgressWorker(
        @NonNull Context context,
        @NonNull WorkerParameters parameters) {
        super(context, parameters);
        // Set initial progress to 0
        setProgressAsync(new Data.Builder().putInt(PROGRESS, 0).build());
    }

    @NonNull
    @Override
    public Result doWork() {
        try {
            // Doing work.
            Thread.sleep(DELAY);
        } catch (InterruptedException exception) {
            // ... handle exception
        }
        // Set progress to 100 after you are done doing your work.
        setProgressAsync(new Data.Builder().putInt(PROGRESS, 100).build());
        return Result.success();
    }
}

观察进度

观察进度信息也很简单,可以使用 getWorkInfoByXXX()getWorkInfoByXXXLiveData() 方法,并引用 WorkInfo

以下是使用 getWorkInfoByIdLiveData API 的示例。

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
            }
    })

链接 Worker

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

例如,假设您的应用有三个 OneTimeWorkRequest 对象:workAworkBworkC。这些任务必须按该顺序运行。如需对这些任务进行排队,请使用 WorkManager.beginWith(OneTimeWorkRequest) 方法,该方法会返回一个 WorkContinuation 实例。

然后,可以使用 WorkContinuationthen(OneTimeWorkRequest)then(List<OneTimeWorkRequest>) 添加剩余的 OneTimeWorkRequest 依赖实例。每次调用 WorkContinuation.then(...) 都会返回一个新的 WorkContinuation 实例。如果添加了一个 List<OneTimeWorkRequest>,这些请求可能会并行运行

最后,调用 WorkContinuation.enqueue() 方法对整个工作链执行入队操作。

WorkManager.getInstance(myContext)
    .beginWith(workA) 
    .then(workB)  
    .then(workC)
    .enqueue()
WorkManager.getInstance(myContext) 
    .beginWith(listOf(workA1, workA2, workA3)) // 这三个 Worker 会并行执行 
    .then(workB) // 依赖 Worker (只会在前面三个 Worker 执行后才会执行) 
    .then(listOf(workC1, workC2))  // 依赖 Worker (会在 workB 执行后才会执行)
    .enqueue()

WorkManager 会根据每个任务的指定约束,按请求的顺序运行任务。如果有任务返回 Result.failure(),整个序列结束。

WorkContinuation构建更复杂的链接任务

如需设置更复杂的链接任务,可以使用 WorkContinuation.combine(List<OneTimeWorkRequest>) 方法来联接多个任务链。
例如,假设您要运行像如下序列:

在这里插入图片描述

如需设置该序列,请创建两个单独的链,然后将它们联接成第三个链:

val chain1 = WorkManager.getInstance(myContext).beginWith(workA).then(workB)
    
val chain2 = WorkManager.getInstance(myContext).beginWith(workC).then(workD)
    
val chain3 = WorkContinuation.combine(Arrays.asList(chain1, chain2)).then(workE)

chain3.enqueue()

在这种情况下,WorkManager 会在 workB 之前运行 workA,在 workD 之前运行 workC。然后会在 workBworkD 都完成后,运行 workE

注意:虽然 WorkManager 会按顺序运行各个子链,但并不保证 chain1 中的任务如何与 chain2 中的任务重叠。例如,workB 可能会在 workC 的前面或后面运行,或者两者也可能会同时运行。唯一可以保证的就是每个子链中的任务将按顺序运行,即 workB 会在 workA 完成之后再启动。

构建 Unique Worker 唯一工作链

WorkManager 可以创建 Unique Worker 唯一工作链,方法是通过调用 beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)(而非 beginWith(OneTimeWorkRequest))。每个 Unique Worker 工作链都有一个名称,WorkManager 不允许同时存在多个具有相同名称的工作链。当您创建新的 Unique Worker 工作链时,如果已存在同名的未完成工作链,您需要指定 WorkManager 应执行什么操作(请参考前面的 冲突解决策略 部分来决定设置哪些冲突策略)。

continuation = WorkManager.getInstance(context)
      .beginUniqueWork(
          Constants.IMAGE_MANIPULATION_WORK_NAME,
          ExistingWorkPolicy.REPLACE,
          OneTimeWorkRequest.from(CleanupWorker::class.java)
      ).thenMaybe<WaterColorFilterWorker>(waterColor)
      .thenMaybe<GrayScaleFilterWorker>(grayScale)
      .thenMaybe<BlurEffectFilterWorker>(blur)
      .then(
          if (save) {
              workRequest<SaveImageToGalleryWorker>(tag = Constants.TAG_OUTPUT)
          } else { // upload 
              workRequest<UploadWorker>(tag = Constants.TAG_OUTPUT)
          }
      )

InputMerger 处理输入冲突

当您链接 OneTimeWorkRequest 实例时,父级工作请求的输出将作为子级的输入传入。例如,在下面的示例中,有 3 个不同的 Worker 并行运行,然后plantName1plantName2plantName3 这些 Worker 的结果的输出将作为输入传递给处理缓存的 cache Worker 请求。最后,cache Worker 的输出将传递到 upload Worker ,由 upload Worker 将结果上传到远程服务器。

WorkManager.getInstance(myContext) 
   .beginWith(listOf(plantName1, plantName2, plantName3)) 
   .then(cache) 
   .then(upload) 
   .enqueue()  

为了管理来自多个父级工作请求的输入,WorkManager 使用 InputMerger

WorkManager 提供两种不同类型的 InputMerger

  • OverwritingInputMerger 会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。

  • ArrayCreatingInputMerger :会尝试合并输入,并在必要时创建数组。

如果您有更具体的用例,则可以创建 InputMerger 的子类来编写自己的用例。

OverwritingInputMerger

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

例如,如果每个输入的Worker都有一个与其各自变量名称(“plantName1”、“plantName2” 和 “plantName3”)匹配的键,传递给 cache Worker的数据将具有三个键值对:

在这里插入图片描述

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

在这里插入图片描述

ArrayCreatingInputMerger

假设我们要保留所有 Worker 的输出,则应使用 ArrayCreatingInputMerger

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

ArrayCreatingInputMerger 将每个键与数组配对。如果每个键都是唯一的,您会得到一系列一元数组:
在这里插入图片描述
如果存在任何键冲突,那么所有对应的值会分组到一个数组中:
在这里插入图片描述

链接中的 Worker 状态

只要 Worker 成功完成(即返回 Result.success()),OneTimeWorkRequest 链便会按顺序执行。运行时,Worker 请求可能会失败或被取消,这会对依赖 Worker 请求产生下游影响。

当第一个 OneTimeWorkRequest 被加入 Worker 请求链队列时,所有后续 Worker 请求会被屏蔽,直到第一个 Worker 请求的工作完成为止。

在这里插入图片描述

在加入队列且满足所有工作约束后,第一个 Worker 请求开始运行。如果工作在根 OneTimeWorkRequestList<OneTimeWorkRequest> 中成功完成(即返回 Result.success()),系统会将下一组依赖 Worker 请求加入队列。

在这里插入图片描述

只要每个 Worker 请求都成功完成, Worker 请求链中的剩余 Worker 请求就会遵循相同的运行模式,直到链中的所有 Worker 都完成为止。这是最简单的用例,通常也是首选用例,但处理错误状态同样重要。

如果在 Worker 处理工作请求时出现错误,会根据您预定义的退避策略来重试该请求。重试请求链中的某个请求意味着,系统将使用提供给该请求的输入数据仅对该请求进行重试。并行运行的所有其他作业均不会受到影响。

在这里插入图片描述

如果未定义重试策略或已用尽,或者以其他方式让 OneTimeWorkRequest 返回 Result.failure() 的某种状态,该 Worker 请求和所有依赖 Worker 请求都会被标记为 FAILED.

在这里插入图片描述

OneTimeWorkRequest 被取消时遵循相同的逻辑。任何依赖 Worker 请求也会被标记为 CANCELLED,并且无法执行其工作。

在这里插入图片描述

请注意,如果要向已失败或已取消 Worker 请求的链附加更多 Worker 请求,新附加的 Worker 请求也会分别标记为 FAILEDCANCELLED。如果您想扩展现有链的工作,请将冲突解决策略 设为ExistingWorkPolicy 中的 APPEND_OR_REPLACE (参考前面介绍)。

创建 Worker 请求链时,依赖 Worker 请求应定义重试策略,以确保始终及时完成工作。失败的工作请求可能会导致链不完整和/或出现意外状态。

自定义 WorkManager 配置和初始化

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

按需初始化

通过按需初始化,您可以仅在需要 WorkManager 时创建该组件,而不必每次应用启动时都创建。这样做可将 WorkManager 从关键启动路径中移出,从而提高应用启动性能。如需使用按需初始化,请执行以下操作:

  • 移除默认初始化程序

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

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

    如果您不在应用中使用应用启动功能,则可以将其彻底移除。

 <!-- If you want to disable android.startup completely. -->
 <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>

如果您使用的 WorkManager 是 2.6 之前的版本,请改为移除 workmanager-init

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    tools:node="remove" />
  • 实现 Configuration.Provider

    Application 类实现 Configuration.Provider 接口,并提供您自己的 Configuration.Provider.getWorkManagerConfiguration() 实现。当您需要使用 WorkManager 时,请务必调用方法 WorkManager.getInstance(Context)WorkManager 会调用应用的自定义 getWorkManagerConfiguration() 方法来查询其 Configuration。(您无需自行调用 WorkManager.initialize()。)

    注意:如果您在初始化 WorkManager 之前调用已弃用的无参数 WorkManager.getInstance() 方法,该方法将抛出异常。即使您不自定义 WorkManager,您也应始终使用 WorkManager.getInstance(Context) 方法。

    以下示例展示了如何自定义 getWorkManagerConfiguration() 实现:

class MyApplication() : Application(), Configuration.Provider {

     override fun getWorkManagerConfiguration() =
           Configuration.Builder()
                .setMinimumLoggingLevel(android.util.Log.INFO)
                .build()
}

注意: 您必须移除默认的初始化程序,自定义的 getWorkManagerConfiguration() 实现才能生效。

在定义自定义 WorkManager 配置后,WorkManager 会在您调用 WorkManager.getInstance(Context) 时进行初始化,而不是在应用启动时自动初始化。

自定义 WorkerFactory

WorkManager 有一个默认的 WorkerFactory ,它在运行时使用反射根据传入的 WorkerclassName来创建 Worker 的对象实例。

如果你创建了一个WorkRequest,然后你用一个不同的类名重构了应用程序,WorkManager将无法找到正确的类,并抛出ClassNotFoundException

你可能想要在你的worker构造函数中添加其他参数。例如,下面的 Worker 需要添加一个与远程服务器通信的Retrofit 服务对象的引用:

class UpvoteStoryWorker(
  appContext: Context, 
  workerParams: WorkerParameters, 
  private val service: UpvoteStoryHttpApi // add 
  )  : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result { 
        return try {
            // Upvote story
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < MAX_NUMBER_OF_RETRY) {
                Result.retry()
            } else {
                Result.failure()
            }
        }
    }
}

如果我们对应用程序进行了这样的更改,它会正常编译,但是,只要我们执行它,WorkManager尝试实例化这个CoroutineWorker类,应用程序就会抛出异常直接崩溃:

Caused by java.lang.NoSuchMethodException: <init> [class android.content.Context, class androidx.work.WorkerParameters]

它提示你找不到正确的init方法来实例化 Worker 对象。此时,我们需要一个自定义的 WorkerFactory 来解决:

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

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? {
	 // This only handles a single Worker, please don’t do this!!
	 // See below for a better way using DelegatingWorkerFactory
        return UpvoteStoryWorker(appContext, workerParameters, DesignerNewsService)

    }
}

在添加自定义的 WorkerFactoryWorkManager之前,同样需要按照前面介绍的先去修改AndroidManifest.xml文件,禁用 WorkManager默认的初始化配置。

然后将自定义的 WorkerFactory 添加到 Configuration 中:

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

如果您的应用程序中只有一个Worker类型,那么到这里就OK了。如果有多个,或者希望将来有更多,那么更好的解决方案是使用 DelegatingWorkerFactory

DelegatingWorkerFactory

除了修改 WorkManager 的配置来直接使用我们自定义的工厂类外,我们可以使用一个DelegatingWorkerFactory,并调用它的 addFactory() 方法向它添加我们自己的WorkerFactory。然后你可以有多个工厂,每个工厂负责处理一个或多个 Worker对象。在DelegatingWorkerFactory中注册你的工厂后,它会处理好协调多个工厂。

在这种情况下,你的工厂类需要注意检查传入的 workerClassName 参数是否能够被正常处理。如果当前工厂类不知道该如何处理传入的 workerClassName ,它将返回null,并且DelegatingWorkerFactory将继续去处理下一个注册的工厂。如果所有注册的工厂都不知道该如何处理一个 workerClass 类,那么它将回退到采用使用反射行为的默认工厂来处理。

下面的示例中,如果工厂类不知道如何处理workerClassName,它将返回null:

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 ->
                // Return null, so that the base class can delegate to the default WorkerFactory.
                null
        }

    }
}

然后我们的WorkManager配置变成下面这样:

class MyApplication : Application(), Configuration.Provider {

     override fun getWorkManagerConfiguration(): Configuration { 
	     val myWorkerFactory = DelegatingWorkerFactory().apply {
	         addFactory(MyWorkerFactory(service))
	         // Add here other factories that you may need in your application
	     } 
	     return Configuration.Builder()
	         .setMinimumLoggingLevel(Log.INFO)
	         .setWorkerFactory(myWorkerFactory)
	         .build()
    }
	...
}

如果您有多个需要不同类型的Worker,您可以创建第二个工厂,并再次调用addFactory添加它。

WorkManager 中的线程处理

WorkManager 提供了四种不同类型的 Worker:

  • Worker 是最简单的实现, WorkManager 会在后台线程中自动运行该基元(您可以将它替换掉)。
  • CoroutineWorker 是为 Kotlin 用户建议的实现。CoroutineWorker 实例公开了后台工作的一个挂起函数。默认情况下,这些实例运行默认的 Dispatcher,但您可以进行自定义。
  • RxWorker 是为 RxJava 用户建议的实现。如果您有很多现有异步代码是用 RxJava 建模的,则应使用 RxWorker
  • ListenableWorkerWorkerCoroutineWorkerRxWorker基类。这个类专为需要与基于回调的异步 API(例如 FusedLocationProviderClient)进行交互并且不使用 RxJava 的 Java 开发者而设计。

Worker 中的线程处理

当您使用 Worker 时,WorkManager 会自动在后台线程中调用 Worker.doWork()。该后台线程来自于 WorkManager 的 Configuration 中指定的 Executor。默认情况下,WorkManager 会为您设置 Executor,但您也可以自己进行自定义。例如,您可以在应用中共享现有的后台 Executor,也可以创建单线程 Executor 以确保所有后台工作都按顺序执行,甚至可以指定一个自定义 Executor。如需自定义 Executor,请确保手动初始化 WorkManager。

在手动配置 WorkManager 时,您可以按以下方式指定 Executor

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

下面是一个简单的 Worker 示例,其会下载网页内容 100 次:

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

    override fun doWork(): ListenableWorker.Result {
        repeat(100) {
            try {
                downloadSynchronously("https://www.google.com")
            } catch (e: IOException) {
                return ListenableWorker.Result.failure()
            }
        }
        return ListenableWorker.Result.success()
    }
}

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

如果当前正在运行的 Worker 因任何原因而停止,它就会收到对 Worker.onStopped() 的调用。在必要的情况下,只需重写此方法或调用 Worker.isStopped(),即可对代码进行检查点处理并释放资源。当上述示例中的 Worker 被停止时,内容的下载可能才完成了一半;但即使该工作器被停止,下载也会继续。如需优化此行为,您可以执行以下操作:

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()
    }
}

Worker 停止后,从 Worker.doWork() 返回什么已不重要;Result 将被忽略。

CoroutineWorker 中的线程处理

对于 Kotlin 用户,WorkManager 为协程提供了一流的支持(请确保添加了work-runtime-ktx依赖)。不要继承 Worker,而应继承 CoroutineWorker,后者包含 doWork() 的挂起版本。例如,如果要构建一个简单的 CoroutineWorker 来执行某些网络操作,示例代码如下:

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 来自定义这个行为。在上面的示例中,如果希望在 Dispatchers.IO 上完成此操作,可进行如下修改:

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 通过取消协程并传播取消信号来自动处理停工情况。您无需执行任何特殊操作来处理停工情况。

在其他进程中运行 CoroutineWorker

如需将 Worker 绑定到 特定进程,可以使用 RemoteCoroutineWorkerListenableWorker 的实现)。

RemoteCoroutineWorker 会使用在构建 Worker请求时于输入数据中提供的两个额外参数绑定到特定进程:ARGUMENT_CLASS_NAMEARGUMENT_PACKAGE_NAME

以下示例演示了如何构建绑定到特定进程的 Worker 请求:

val PACKAGE_NAME = "com.example.background.multiprocess" 
val serviceName = RemoteWorkerService::class.java.name
val componentName = ComponentName(PACKAGE_NAME, serviceName)

val data: Data = Data.Builder()
   .putString(ARGUMENT_PACKAGE_NAME, componentName.packageName)
   .putString(ARGUMENT_CLASS_NAME, componentName.className)
   .build()

return OneTimeWorkRequest.Builder(ExampleRemoteCoroutineWorker::class.java)
   .setInputData(data)
   .build()

对于每个 RemoteWorkerService,需要在 AndroidManifest.xml 文件中添加服务定义:

<manifest ... >
    <service
            android:name="androidx.work.multiprocess.RemoteWorkerService"
            android:exported="false"
            android:process=":worker1" />

        <service
            android:name=".RemoteWorkerService2"
            android:exported="false"
            android:process=":worker2" />
    ...
</manifest>

创建和管理长时间运行的 worker

Kotlin 开发者应使用 CoroutineWorker.setForeground() 方法来设置前台服务:

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

   private val notificationManager =
       context.getSystemService(Context.NOTIFICATION_SERVICE) as
               NotificationManager

   override suspend fun doWork(): Result {
       val inputUrl = inputData.getString(KEY_INPUT_URL)
                      ?: return Result.failure()
       val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
                      ?: return Result.failure()
       // Mark the Worker as important
       val progress = "Starting Download"
       setForeground(createForegroundInfo(progress))
       download(inputUrl, outputFile)
       return Result.success()
   }

   private fun download(inputUrl: String, outputFile: String) {
       // Downloads a file and updates bytes read
       // Calls setForeground() periodically when it needs to update
       // the ongoing Notification
   }
   // Creates an instance of ForegroundInfo which can be used to update the
   // ongoing notification.
   private fun createForegroundInfo(progress: String): ForegroundInfo {
       val id = applicationContext.getString(R.string.notification_channel_id)
       val title = applicationContext.getString(R.string.notification_title)
       val cancel = applicationContext.getString(R.string.cancel_download)
       // This PendingIntent can be used to cancel the worker
       val intent = WorkManager.getInstance(applicationContext)
               .createCancelPendingIntent(getId())

       // Create a Notification channel if necessary
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           createChannel()
       }

       val notification = NotificationCompat.Builder(applicationContext, id)
           .setContentTitle(title)
           .setTicker(title)
           .setContentText(progress)
           .setSmallIcon(R.drawable.ic_work_notification)
           .setOngoing(true)
           // Add the cancel action to the notification which can
           // be used to cancel the worker
           .addAction(android.R.drawable.ic_delete, cancel, intent)
           .build()

       return ForegroundInfo(notificationId, notification)
   }

   @RequiresApi(Build.VERSION_CODES.O)
   private fun createChannel() {
       // Create a Notification channel
   }

   companion object {
       const val KEY_INPUT_URL = "KEY_INPUT_URL"
       const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
   }
}

对于 Java 开发者,应使用 ListenableWorkerWorkersetForegroundAsync() API,该 API 会返回 ListenableFuture<Void>

下面是一个简单的示例,说明了一个下载文件的长时间运行 worker。此 worker 会跟踪进度,以更新持续显示下载进度的 Notification

public class DownloadWorker extends Worker {
   private static final String KEY_INPUT_URL = "KEY_INPUT_URL";
   private static final String KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME";

   private NotificationManager notificationManager;

   public DownloadWorker(
       @NonNull Context context,
       @NonNull WorkerParameters parameters) {
           super(context, parameters);
           notificationManager = (NotificationManager)
               context.getSystemService(NOTIFICATION_SERVICE);
   }

   @NonNull
   @Override
   public Result doWork() {
       Data inputData = getInputData();
       String inputUrl = inputData.getString(KEY_INPUT_URL);
       String outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME);
       // Mark the Worker as important
       String progress = "Starting Download";
       setForegroundAsync(createForegroundInfo(progress));
       download(inputUrl, outputFile);
       return Result.success();
   }

   private void download(String inputUrl, String outputFile) {
       // Downloads a file and updates bytes read
       // Calls setForegroundAsync(createForegroundInfo(myProgress))
       // periodically when it needs to update the ongoing Notification.
   }

   @NonNull
   private ForegroundInfo createForegroundInfo(@NonNull String progress) {
       // Build a notification using bytesRead and contentLength

       Context context = getApplicationContext();
       String id = context.getString(R.string.notification_channel_id);
       String title = context.getString(R.string.notification_title);
       String cancel = context.getString(R.string.cancel_download);
       // This PendingIntent can be used to cancel the worker
       PendingIntent intent = WorkManager.getInstance(context)
               .createCancelPendingIntent(getId());

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           createChannel();
       }

       Notification notification = new NotificationCompat.Builder(context, id)
               .setContentTitle(title)
               .setTicker(title)
               .setSmallIcon(R.drawable.ic_work_notification)
               .setOngoing(true)
               // Add the cancel action to the notification which can
               // be used to cancel the worker
               .addAction(android.R.drawable.ic_delete, cancel, intent)
               .build();

       return new ForegroundInfo(notificationId, notification);
   }

   @RequiresApi(Build.VERSION_CODES.O)
   private void createChannel() {
       // Create a Notification channel
   }
}

将前台服务类型添加到长时间运行的 worker

如果应用以 Android 10(API 级别 29)或更高版本为目标平台,且包含需要位置信息访问权限的长时间运行的 worker,请指明该 worker 使用 location 的前台服务类型。此外,如果应用以 Android 11(API 级别 30)或更高版本为目标平台,且包含需要访问摄像头或麦克风的长时间运行的 worker,则需要分别声明 camera 或 microphone 前台服务类型。

如需添加这些前台服务类型,请完成以下各步骤。

  • 在应用清单文件中声明 worker 的前台服务类型

    在以下示例中,worker 需要位置信息和麦克风访问权限:

// AndroidManifest.xml
<service
   android:name="androidx.work.impl.foreground.SystemForegroundService"
   android:foregroundServiceType="location|microphone"
   tools:node="merge" />

注意:tools:node="merge"会将上述代码段中的 <service> 元素声明与 WorkManager 自身清单中的 SystemForegroundService 定义的声明合并在一起。

  • 在运行时指定前台服务类型

    当您调用 setForeground()setForegroundAsync() 时,请指定前台服务类型 FOREGROUND_SERVICE_TYPE_LOCATIONFOREGROUND_SERVICE_TYPE_CAMERAFOREGROUND_SERVICE_TYPE_MICROPHONE,如以下代码段所示。

// MyLocationAndMicrophoneWorker
private fun createForegroundInfo(progress: String): ForegroundInfo {
     // ...
     return ForegroundInfo(NOTIFICATION_ID, notification, 
         FOREGROUND_SERVICE_TYPE_LOCATION or FOREGROUND_SERVICE_TYPE_MICROPHONE) 
} 

注意:在运行时,您可以选择限制长时间运行的 worker 对数据的访问,方法是传递您已在清单文件中声明的一部分前台服务类型。

调试 WorkManager

启用日志记录

如需启用日志记录功能,请参考前面介绍的自定义初始化部分。首先,你需要修改 AndroidManifest.xml 清单文件使用合并规则 remove 来停用 WorkManager 的默认初始化配置:

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    tools:node="remove"/>

在停用WorkManager 默认初始化程序后,在Application中执行按需初始化:

class MyApplication() : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration() =
        Configuration.Builder()
            .setMinimumLoggingLevel(android.util.Log.DEBUG)
            .build()
}

完成此项更改后,WorkManager 将在日志记录为 DEBUG 的前提下运行。更好的选择是仅针对应用的调试 build 对 WorkManager 进行此设置,方法如下:

class BlurApplication() : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration(): Configuration {
        return if (BuildConfig.DEBUG) {
            Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.DEBUG)
                    .build()
        } else {
            Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.ERROR)
                    .build()
        }
    }
	...
}

启用 DEBUG 日志记录后,在 Logcat 控制台中会开始显示更多包含标记前缀 WM- 的日志。

使用 adb shell dumpsys jobscheduler

在 API 级别 23 或更高版本设备上,可以运行命令 adb shell dumpsys jobscheduler 来查看归属于自己应用的作业列表。会输出以下类似的内容:

JOB #u0a172/4: 6412553 com.google.android.youtube/androidx.work.impl.background.systemjob.SystemJobService
  u0a172 tag=*job*/com.google.android.youtube/androidx.work.impl.background.systemjob.SystemJobService
  Source: uid=u0a172 user=0 pkg=com.google.android.youtube
  JobInfo:
    Service: com.google.android.youtube/androidx.work.impl.background.systemjob.SystemJobService
    Requires: charging=false batteryNotLow=false deviceIdle=false
    Extras: mParcelledData.dataSize=180
    Network type: NetworkRequest [ NONE id=0, [ Capabilities: NOT_METERED&INTERNET&NOT_RESTRICTED&TRUSTED&VALIDATED Uid: 10172] ]
    Minimum latency: +1h29m59s687ms
    Backoff: policy=1 initial=+30s0ms
    Has early constraint
  Required constraints: TIMING_DELAY CONNECTIVITY [0x90000000]
  Satisfied constraints: DEVICE_NOT_DOZING BACKGROUND_NOT_RESTRICTED WITHIN_QUOTA [0x3400000]
  Unsatisfied constraints: TIMING_DELAY CONNECTIVITY [0x90000000]
  Tracking: CONNECTIVITY TIME QUOTA
  Implicit constraints:
    readyNotDozing: true
    readyNotRestrictedInBg: true
  Standby bucket: RARE
  Base heartbeat: 0
  Enqueue time: -51m29s853ms
  Run time: earliest=+38m29s834ms, latest=none, original latest=none
  Last run heartbeat: 0
  Ready: false (job=false user=true !pending=true !active=true !backingup=true comp=true)

使用 WorkManager 时,负责管理 worker 执行的组件为 SystemJobService(搭载 API 级别 23 或更高版本)。您应该查找归属于您的软件包名称和 androidx.work.impl.background.systemjob.SystemJobService 的作业实例。

对于每个作业,命令的输出都会列出必需、满足和不满足约束条件。您应该检查是否完全满足Worker的约束条件。

您可以利用它检查最近是否调用了 SystemJobService。输出还包括最近执行的作业的历史记录:

Job history:
     -1h35m26s440ms   START: #u0a107/9008 com.google.android.youtube/androidx.work.impl.background.systemjob.SystemJobService
     -1h35m26s362ms  STOP-P: #u0a107/9008 com.google.android.youtube/androidx.work.impl.background.systemjob.SystemJobService app called jobFinished

从 WorkManager 2.4.0 及更高版本请求诊断信息

在应用的调试 build 中,您可以使用以下命令从 WorkManager 2.4.0 及更高版本请求诊断信息:

adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS" -p "<your_app_package_name>"

这提供了以下方面的信息:

  • 在过去 24 小时内完成的工作请求。
  • 目前正在运行的工作请求。
  • 预定运行的工作请求。

诊断信息如下所示(输出通过 logcat 显示):

adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS" -p "androidx.work.integration.testapp"

adb logcat
...
2020-02-13 14:21:37.990 29528-29660/androidx.work.integration.testapp I/WM-DiagnosticsWrkr: Recently completed work:
2020-02-13 14:21:38.083 29528-29660/androidx.work.integration.testapp I/WM-DiagnosticsWrkr: Id  Class Name   State  Unique Name Tags
    08be261c-2def-4bd6-a716-1e4410968dc4     androidx.work.impl.workers.DiagnosticsWorker    SUCCEEDED  null    androidx.work.impl.workers.DiagnosticsWorker
    48ce04f1-8df9-450b-96ec-6eceabb9c690     androidx.work.impl.workers.DiagnosticsWorker    SUCCEEDED  null    androidx.work.impl.workers.DiagnosticsWorker
    c46f4699-c384-440c-a10e-26d56ce02963     androidx.work.impl.workers.DiagnosticsWorker    SUCCEEDED  null    androidx.work.impl.workers.DiagnosticsWorker
    ce125372-046e-484e-949f-9abb35ce62c3     androidx.work.impl.workers.DiagnosticsWorker    SUCCEEDED  null    androidx.work.impl.workers.DiagnosticsWorker
    72887ddd-8ed1-4018-b798-fac218e95e16     androidx.work.impl.workers.DiagnosticsWorker    SUCCEEDED  null    androidx.work.impl.workers.DiagnosticsWorker
    dcff3d61-320d-4996-8644-5d97944bd09c     androidx.work.impl.workers.DiagnosticsWorker    SUCCEEDED  null    androidx.work.impl.workers.DiagnosticsWorker
    acab0bf7-6087-43ad-bdb5-be0df9195acb     androidx.work.impl.workers.DiagnosticsWorker    SUCCEEDED  null    androidx.work.impl.workers.DiagnosticsWorker
    23136bcd-01dd-46eb-b910-0fe8a140c2a4     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
    245f4879-c6d2-4997-8130-e4e90e1cab4c     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
    17d05835-bb61-429a-ad11-fe43fc320a54     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
    e95f12be-4b0c-4e64-88da-8ee07a31e42f     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
    431c3ec2-4a55-469b-b50b-4072d35f1232     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
    883a388f-f911-4098-9143-37bd8fbc098a     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
    b904163c-6822-4299-8d5a-78df49b7e53d     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
    453fd7b9-2b16-45b9-abc5-3d2ce7b6a4ba     androidx.work.integration.testapp.ToastWorker   SUCCEEDED  null    androidx.work.integration.testapp.ToastWorker
2020-02-13 14:21:38.083 29528-29660/androidx.work.integration.testapp I/WM-DiagnosticsWrkr: Running work:
2020-02-13 14:21:38.089 29528-29660/androidx.work.integration.testapp I/WM-DiagnosticsWrkr: Id  Class Name   State  Unique Name Tags
    b87c8a4f-4ac6-4e25-ba3e-4cea53ce468a     androidx.work.impl.workers.DiagnosticsWorker    RUNNING    null    androidx.work.impl.workers.DiagnosticsWorker
...

WorkManager 源码解析

WorkManager的初始化

首先 WorkManager 的初始化并不是如下代码:

WorkManager.getInstance(this)

而是在 AndroidManifest.xml 清单文件中配置的 provider 指定的 WorkManagerInitializer 类中:

在这里插入图片描述

public final class WorkManagerInitializer implements Initializer<WorkManager> {
    private static final String TAG = Logger.tagWithPrefix("WrkMgrInitializer");

    @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);
    }
 
    @Override
    public List<Class<? extends androidx.startup.Initializer<?>>> dependencies() {
        return Collections.emptyList();
    }
}

在以前的版本中,WorkManagerInitializer 是继承的 ContentProvider,但是最新的版本中已经改为继承 Initializer 接口,该接口可以负责在App启动过程中初始化各个库。

由于WorkManager是个单例,在此时WorkManager就已经被初始化了,在initialize()之前,会创建一个默认的ConfigurationConfiguration设置了许多属性,用来管理和调度工作的方式。

看一下 initialize()的实现,它是定义在WorkManager的子类WorkManagerImpl中实现的:

public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        synchronized (sLock) {
            ...... 
            if (sDelegatedInstance == null) {
                context = context.getApplicationContext();
                if (sDefaultInstance == null) {
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }

这里如果时 sDefaultInstancenullWorkManager会先创建一个默认的WorkManagerImpl对象,并且会创建一个WorkManagerTaskExecutor 对象,用来执行WorkManager的任务。

看一下 WorkManagerImpl 的构造方法:

	// WorkManagerImpl.java 
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor) {
        this(context,
                configuration,
                workTaskExecutor,
                context.getResources().getBoolean(R.bool.workmanager_test_configuration));
    }
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            boolean useTestDatabase) {
        this(context,
                configuration,
                workTaskExecutor,
                WorkDatabase.create(
                        context.getApplicationContext(),
                        workTaskExecutor.getBackgroundExecutor(),
                        useTestDatabase)
        );
    }
    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 构造函数中调用 WorkDatabase.create() 创建了数据库WorkDatabase对象,WorkDatabase负责将任务列表序列化到本地,记录每一个任务的属性,执行条件,执行顺序及执行状态等,从而保证任务在冷启动或硬件重启后,能够根据条件继续执行。

WorkManagerImpl 最终调用的构造方法中有三个重要的初始化步骤

步骤一:createSchedulers()创建的Schedulers进行任务调度。

	// WorkManagerImpl.java 
    public List<Scheduler> createSchedulers(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor taskExecutor) {
         // return一个Scheduler数组。其中GreedyScheduler()是实例化好了的  
        return Arrays.asList(
                Schedulers.createBestAvailableBackgroundScheduler(context, this),
                // Specify the task executor directly here as this happens before internalInit.
                // GreedyScheduler creates ConstraintTrackers and controllers eagerly.
                new GreedyScheduler(context, configuration, taskExecutor, this)); // 贪婪调度程序
    }

步骤二:Processor()用来管理Schedulers的执行

public WorkManagerImpl(...) {
        ...
        List<Scheduler> schedulers =
                createSchedulers(applicationContext, configuration, workTaskExecutor);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
       ...
    }

步骤三:internalInit()真正的初始化。

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

        context = context.getApplicationContext();
        mContext = context;
        mConfiguration = configuration;
        mWorkTaskExecutor = workTaskExecutor;
        mWorkDatabase = workDatabase;
        mSchedulers = schedulers;
        mProcessor = processor;
        mPreferenceUtils = new PreferenceUtils(workDatabase);
        mForceStopRunnableCompleted = false;

        // Check for direct boot mode
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && context.isDeviceProtectedStorage()) {
            throw new IllegalStateException("Cannot initialize WorkManager in direct boot mode");
        }
		
        // 检查应用程序强制停止  例如:正在执行任务的时候,手机关机了,或者发生了意外,这里就会重试之前失败的任务 
        mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
    }

WorkManager的初始化总结:

  • ① WorkManager的初始化是由WorkManagerInitializer这个类负责的,实现类是WorkManagerImpl
  • ② 会初始化 Configuration,WorkManagerTaskExecutor,WorkDatabase,Schedulers,Processor
  • ③ 创建 GreedyScheduler 调度器等
  • ④ 发现未完成的,需要重新执行的任务(之前意外中断的继续执行)

WorkManager 的非约束条件任务的执行源码分析

WorkManager.getInstance(this).enqueue(request)

进入enqueue()源码:

	// WorkManager.java
    public final Operation enqueue(@NonNull WorkRequest workRequest) {
        return enqueue(Collections.singletonList(workRequest));
    }
    public abstract Operation enqueue(@NonNull List<? extends WorkRequest> requests);
	// WorkManagerImpl.java
	public Operation enqueue(@NonNull List<? extends WorkRequest> requests) {
        // This error is not being propagated as part of the Operation, as we want the
        // app to crash during development. Having no workRequests is always a developer error.
        if (requests.isEmpty()) {
            throw new IllegalArgumentException("enqueue needs at least one WorkRequest.");
        }
        return new WorkContinuationImpl(this, requests).enqueue();
    }

这里创建了一个WorkContinuationImpl对象,再执行enqueue()方法。WorkContinuationImplWorkContinuation的子类:

public WorkContinuationImpl(@NonNull WorkManagerImpl workManagerImpl,
            @Nullable String name,
            @NonNull ExistingWorkPolicy existingWorkPolicy,
            @NonNull List<? extends WorkRequest> work,
            @Nullable List<WorkContinuationImpl> parents) {
        mWorkManagerImpl = workManagerImpl;
        mName = name;
        mExistingWorkPolicy = existingWorkPolicy;
        mWork = work;
        mParents = parents;
        mIds = new ArrayList<>(mWork.size());
        mAllIds = new ArrayList<>();
        if (parents != null) {
            for (WorkContinuationImpl parent : parents) {
                mAllIds.addAll(parent.mAllIds);
            }
        }
        for (int i = 0; i < work.size(); i++) {
            String id = work.get(i).getStringId();
            mIds.add(id);
            mAllIds.add(id);
        }
    }

WorkContinuationImpl构造方法中会保存任务相关的所有信息,比如 WorkManagerWorkRequest,父WorkContinuation … 再接着继续看WorkContinuationImplenqueue()方法的实现:

	// WorkContinuationImpl.java
    @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方法提交的任务会交给EnqueueRunnable执行,这里主要是创建了一个 EnqueueRunnable 对象丢到调度器中去执行。

看一下 EnqueueRunnablerun方法源码:

	// EnqueueRunnable.java 
	@Override
    public void run() {
        try {
            if (mWorkContinuation.hasCycles()) {
                throw new IllegalStateException(
                        String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
            }
            // addToDatabase()的作用是把WorkSpec存入到数据库,并对任务的状态进行校验
            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));
        }
    }
    public void scheduleWorkInBackground() {
        WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
        // 重点
        Schedulers.schedule(
                workManager.getConfiguration(),
                workManager.getWorkDatabase(),
                workManager.getSchedulers());
    }

在EnqueueRunnable的run方法中,会将入队的任务信息入库操作(使用Room组件),这就是为什么即使App被杀死了也会执行到任务。

而最终调用了 Schedulers.schedule() 方法进行调度,传入了Configuration, WorkDatabase, Scheduler这三个对象。

看一下 schedule() 函数:

	// Schedulers.java
	public static void schedule(Configuration configuration, WorkDatabase workDatabase, List<Scheduler> schedulers) { 
		...... 
        workDatabase.beginTransaction();
        try {
            ...... 
            if (eligibleWorkSpecsForLimitedSlots != null
                    && eligibleWorkSpecsForLimitedSlots.size() > 0) {
                ......
                for (WorkSpec workSpec : eligibleWorkSpecsForLimitedSlots) {
                	// 先进行了一系列的数据库操作,然后开始根据条件每个任务进行调度,更新保存数据库等操作
                    workSpecDao.markWorkSpecScheduled(workSpec.id, now);
                }
            }
            workDatabase.setTransactionSuccessful();
        }
        ...... 
        if (eligibleWorkSpecsForLimitedSlots != null
                && eligibleWorkSpecsForLimitedSlots.size() > 0) { 
            ...... 
            for (Scheduler scheduler : schedulers) {
                if (scheduler.hasLimitedSchedulingSlots()) {
                	// scheduler.schedule()对每个任务进行调度处理
                    scheduler.schedule(eligibleWorkSpecsArray);
                }
            }
        }

        if (allEligibleWorkSpecs != null && allEligibleWorkSpecs.size() > 0) {
            ...... 
            for (Scheduler scheduler : schedulers) {
                if (!scheduler.hasLimitedSchedulingSlots()) {
                    scheduler.schedule(enqueuedWorkSpecsArray);
                }
            }
        }
    }

上面代码中会调用scheduler.schedule()对每个任务进行调度处理,我们是没有约束的一次性任务,所以看一下GreedyScheduler对于schedule()方法的实现:

	// GreedyScheduler.java
	@Override
    public void schedule(@NonNull WorkSpec... workSpecs) {
        ......
        for (WorkSpec workSpec : workSpecs) {
            ......
            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()) {
                       ......
                    } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                       ......
                    } else {
                        constrainedWorkSpecs.add(workSpec);
                        constrainedWorkSpecIds.add(workSpec.id);
                    }
                } else {
                    ......
                    // 注意:由于无约束条件的会走这里 
                    mWorkManagerImpl.startWork(workSpec.id);
                }
            }
        } 
        ......
    }

继续看WorkManagerImpl.startWork()方法:

 public void startWork(@NonNull String workSpecId) {
     startWork(workSpecId, null);
 }
 public void startWork(String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras) {
    mWorkTaskExecutor.executeOnBackgroundThread(
                    new StartWorkRunnable(this, workSpecId, runtimeExtras));
}

可以看到创建了一个 StartWorkRunnable 对象丢入调度器执行。

	// StartWorkRunnable.java
    @Override
    public void run() {
        mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
    }

StartWorkRunnablerun方法是交给了 WorkManagerImplProcessor对象去处理:

	// Processor.java
	public boolean startWork(String id, WorkerParameters.RuntimeExtras runtimeExtras) {

        WorkerWrapper workWrapper;
        synchronized (mLock) { 
            if (isEnqueued(id)) { 
                return false;
            } 
            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);
        }
        // 将封装的workWrapper 对象丢到调度器中执行
        mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper); 
        return true;
    }

下面进入WorkerWrapper类看看WorkerWrapper的run()方法的实现:

	// WorkerWrapper.java
	@Override
    public void run() {
        mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
        mWorkDescription = createWorkDescription(mTags);
        runWorker();
    }

    private void runWorker() {
    	......
    	final WorkerParameters params = new WorkerParameters(.....);

        if (mWorker == null) {
        	// 调用默认的工厂类反射创建Worker对象
            mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
                    mAppContext,
                    mWorkSpec.workerClassName,
                    params);
        }
    	......  
        if (trySetRunning()) { 
            final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
            final WorkForegroundRunnable foregroundRunnable =
                    new WorkForegroundRunnable(......);
            mWorkTaskExecutor.getMainThreadExecutor().execute(foregroundRunnable);

            final ListenableFuture<Void> runExpedited = foregroundRunnable.getFuture();
            runExpedited.addListener(new Runnable() {
                @Override
                public void run() {
                    try {
                        runExpedited.get();
                        // 这里调用了mWorker.startWork()方法开始执行Worker的任务
                        mInnerFuture = mWorker.startWork();
                        future.setFuture(mInnerFuture);
                    } catch (Throwable e) {
                        future.setException(e);
                    }
                }
            }, mWorkTaskExecutor.getMainThreadExecutor());
         }
         ......  
	}

继续看 Worker.startWork() 方法:

	// Worker.java
	@Override
    public final @NonNull ListenableFuture<Result> startWork() {
        mFuture = SettableFuture.create();
        getBackgroundExecutor().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Result result = doWork(); // 调用 doWork() 开始干活
                    mFuture.set(result);
                } catch (Throwable throwable) {
                    mFuture.setException(throwable);
                }

            }
        });
        return mFuture;
    }

可以看到这里 Worker.startWork() 方法中最终在后台调度器中执行了我们定义的Worker对象的doWork() 开始执行任务。

WorkManager的 无约束的任务的执行源码总结:

  1. WorkManager执行了enqueue()后,创建WorkContinuationImpl对象执行enqueue()方法。
  2. WorkContinuationImpl持有的EnqueueRunnable对象将任务添加到db,并交给Schedulers去调度。
  3. Schedulers将任务交给每一个Scheduler去处理,GreedyScheduler会先处理这个任务。
  4. GreedyScheduler经过一系列判断后,调用WorkManager的startWork()方法执行这种一次性,非延迟,无约束的任务。
  5. WorkManager持有的StartWorkRunnable对象会将任务交给Processor去处理,执行startWork()方法。
  6. Processor创建一个WorkerWrapper对象,由它去调用Worker的startWork()方法,进而调用doWork()方法。
    在这里插入图片描述

WorkManager的有约束条件任务的执行源码分析

约束条件会在清单文件中注册对应的广播接收器,以低电量为例:

<receiver android:directBootAware="false" android:enabled="false"
	android:exported="false"
	android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy">
		<intent-filter>
			<action android:name="android.intent.action.BATTERY_OKAY"/>
			<action android:name="android.intent.action.BATTERY_LOW"/>
		</intent-filter>
</receiver>

当在电量变化时,收到BATTERY_LOW的广播,会在BatteryNotLowProxyonReceive()进行处理:

abstract class ConstraintProxy extends BroadcastReceiver {
    private static final String TAG = Logger.tagWithPrefix("ConstraintProxy");

    @Override
    public void onReceive(Context context, Intent intent) {
        Logger.get().debug(TAG, String.format("onReceive : %s", intent));
        Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
        context.startService(constraintChangedIntent);
    }

    /**
     * Proxy for Battery Not Low constraint
     */
    public static class BatteryNotLowProxy extends ConstraintProxy {
    }

    /**
     * Proxy for Battery Charging constraint
     */
    public static class BatteryChargingProxy extends ConstraintProxy {
    }

    /**
     * Proxy for Storage Not Low constraint
     */
    public static class StorageNotLowProxy extends ConstraintProxy {
    }

    /**
     * Proxy for Network State constraints
     */
    public static class NetworkStateProxy extends ConstraintProxy {
    }
}

可以看到 ConstraintProxy 总共有四个子类,分别对应四种约束类型。在onReceive()中会调用createConstraintsChangedIntent()创建一个SystemAlarmServiceIntent, 然后启动该Service。

    static Intent createConstraintsChangedIntent(@NonNull Context context) {
        Intent intent = new Intent(context, SystemAlarmService.class);
        intent.setAction(ACTION_CONSTRAINTS_CHANGED);
        return intent;
    }

看一下 SystemAlarmServiceonStartCommand() 方法:

	// SystemAlarmService.java
	 @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        ...... 
        if (intent != null) {
            mDispatcher.add(intent, startId);
        }  
        return Service.START_REDELIVER_INTENT;
    }

这里调用了 SystemAlarmDispatcheradd 方法:

	// SystemAlarmDispatcher.java
    public boolean add(@NonNull final Intent intent, final int startId) {
        ...... 
        intent.putExtra(KEY_START_ID, startId);
        synchronized (mIntents) {
            boolean hasCommands = !mIntents.isEmpty();
            mIntents.add(intent);
            if (!hasCommands) { 
                processCommand();
            }
        }
        return true;
    }

这里又会调用 processCommand() 方法:

	// SystemAlarmDispatcher.java
    private void processCommand() {
        ......
        try {
            processCommandLock.acquire();
            // Process commands on the background thread.
            mWorkManager.getWorkTaskExecutor().executeOnBackgroundThread(new Runnable() {
                @Override
                public void run() {
                    ......
                    if (mCurrentIntent != null) { 
                        ......
                        try {
                            ......
                            // 重点
                            mCommandHandler.onHandleIntent(mCurrentIntent, startId,
                                    SystemAlarmDispatcher.this);
                        } catch (Throwable throwable) {
                            ... 
                        } 
                    }
                }
            });
        } 
        ......
    }

这里会调用 mCommandHandler.onHandleIntent() 方法:

// CommandHandler.java
void onHandleIntent(Intent intent, int startId, SystemAlarmDispatcher dispatcher) { 
        String action = intent.getAction(); 
        if (ACTION_CONSTRAINTS_CHANGED.equals(action)) { // 约束条件发生变化
            handleConstraintsChanged(intent, startId, dispatcher); 
        }  
        ......
}
private void handleConstraintsChanged(Intent intent, int startId, SystemAlarmDispatcher dispatcher) {
    ConstraintsCommandHandler changedCommandHandler =
            new ConstraintsCommandHandler(mContext, startId, dispatcher);
    changedCommandHandler.handleConstraintsChanged();
}
// ConstraintsCommandHandler.java
void handleConstraintsChanged() {
        List<WorkSpec> candidates = mDispatcher.getWorkManager().getWorkDatabase()
                .workSpecDao()
                .getScheduledWork();
        ConstraintProxy.updateAll(mContext, candidates);
        ......
        for (WorkSpec workSpec : eligibleWorkSpecs) {
            String workSpecId = workSpec.id;
            Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
            // 交给 SystemAlarmDispatcher
            mDispatcher.postOnMainThread(
                    new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));
        }
        mWorkConstraintsTracker.reset();
}

其中 CommandHandler.createDelayMetIntent()代码如下:

static Intent createDelayMetIntent(@NonNull Context context, @NonNull String workSpecId) {
    Intent intent = new Intent(context, SystemAlarmService.class);
    intent.setAction(ACTION_DELAY_MET);
    intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
    return intent;
}

这里CommandHandler.createDelayMetIntent()方法会创建一个actionACTION_DELAY_METIntent,然后该Intent交给了 SystemAlarmDispatcherAddRunnable

    static class AddRunnable implements Runnable {
        private final SystemAlarmDispatcher mDispatcher; 
		...
        @Override
        public void run() {
            mDispatcher.add(mIntent, mStartId);
        }
    }

这里发现又回到了前面分析过的 SystemAlarmDispatcheradd 方法中。有点类似一个状态机循环。然后又会循环调用 processCommand() -> mCommandHandler.onHandleIntent()

// CommandHandler.java
void onHandleIntent(Intent intent, int startId, SystemAlarmDispatcher dispatcher) { 
        String action = intent.getAction(); 
        if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
            handleConstraintsChanged(intent, startId, dispatcher);
        } else if (ACTION_RESCHEDULE.equals(action)) {
            handleReschedule(intent, startId, dispatcher);
        } else {
            Bundle extras = intent.getExtras();
            if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
                ......
            } else {
                if (ACTION_SCHEDULE_WORK.equals(action)) {
                    handleScheduleWorkIntent(intent, startId, dispatcher);
                } else if (ACTION_DELAY_MET.equals(action)) { // 这里匹配了 ACTION_DELAY_MET
                    handleDelayMet(intent, startId, dispatcher); // 重点
                } 
                .....
            }
        }
    }

handleDelayMet() 源码如下:

// CommandHandler.java
private void handleDelayMet(Intent intent, int startId, SystemAlarmDispatcher dispatcher) { 
        Bundle extras = intent.getExtras();
        synchronized (mLock) {
            String workSpecId = extras.getString(KEY_WORKSPEC_ID); 
            if (!mPendingDelayMet.containsKey(workSpecId)) {
                DelayMetCommandHandler delayMetCommandHandler =
                        new DelayMetCommandHandler(mContext, startId, workSpecId, dispatcher);
                mPendingDelayMet.put(workSpecId, delayMetCommandHandler);
                delayMetCommandHandler.handleProcessWork(); // 重点
            } else {
                ......
            }
        }
    }

这里会调用 DelayMetCommandHandlerhandleProcessWork() 方法

// DelayMetCommandHandler.java
void handleProcessWork() {
        mWakeLock = WakeLocks.newWakeLock(
                mContext,
                String.format("%s (%s)", mWorkSpecId, mStartId)); 
        mWakeLock.acquire(); 
        WorkSpec workSpec = mDispatcher.getWorkManager()
                .getWorkDatabase()
                .workSpecDao()
                .getWorkSpec(mWorkSpecId);
 		...... 
        mHasConstraints = workSpec.hasConstraints(); 
        if (!mHasConstraints) {
            // 重点
            onAllConstraintsMet(Collections.singletonList(mWorkSpecId));
        } else {
            // Allow tracker to report constraint changes
            mWorkConstraintsTracker.replace(Collections.singletonList(workSpec));
        }
    }

于是来到了 onAllConstraintsMet() 方法:

	// DelayMetCommandHandler.java
    @Override
    public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
        ......  
        synchronized (mLock) {
            if (mCurrentState == STATE_INITIAL) {
                mCurrentState = STATE_START_REQUESTED;
                // 执行 Processor 的 startWork() 方法
                boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);
				......  
            }  
        }
    }

这里调用了 mDispatcher.getProcessor().startWork(mWorkSpecId), 这里mDispatcherSystemAlarmDispatcher ,其 getProcessor()方法返回的就是WorkManagerImplProcessor 对象。

再继续跟下去就跟前面非约束条件的任务执行流程类似, Processor 最终会走 WorkerWrapper.runWorker() -> Worker.startWork() -> Worker.doWork() 所以殊途同归。

WorkManager的有约束条件任务的执行总结:

在这里插入图片描述


管理设备唤醒状态

使屏幕保持开启状态

某些应用需要使屏幕保持开启状态,例如游戏或电影应用。要实现此目标,最好的方法是在您的 Activity 中(仅在 Activity 中,切勿在服务或其他应用组件中)使用 FLAG_KEEP_SCREEN_ON。例如:

class MainActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    }
}

这种方法的优点是,与唤醒锁定不同,它不需要特殊权限,并且平台可以正确管理用户在不同应用之间的切换,您的应用无需担心释放未使用的资源。

实现此目标的另一种方法是,在应用的布局 XML 文件中,使用 android:keepScreenOn 属性:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:keepScreenOn="true">
        ...
</RelativeLayout>

使用 android:keepScreenOn="true" 功效等同于 FLAG_KEEP_SCREEN_ON。您可以使用最适合您的应用的任意一种方法。在 Activity 中以编程方式设置标记的优势在于,您可以选择稍后以编程方式清除该标记,从而使屏幕可以关闭。

使 CPU 保持运行状态

如果您需要使 CPU 保持运行状态,以便在设备进入休眠模式之前完成某项工作,可以使用一项称为“唤醒锁定”的 PowerManager 系统服务功能。唤醒锁定可使应用控制主机设备的电源状态。

创建和持有唤醒锁定会对主机设备的电池续航时间产生重大影响。因此,您应仅在绝对必要时使用唤醒锁定,并持有尽可能短的时间。例如,您绝不需要在 Activity 中使用唤醒锁定。如上所述,如果您希望屏幕在 Activity 中保持开启状态,请使用 FLAG_KEEP_SCREEN_ON

使用唤醒锁定的一种合理情形是,某项后台服务需要获取唤醒锁定,以便 CPU 在屏幕关闭时保持运行状态,可以完成相关工作。再次声明,由于这种做法会影响电池续航时间,因此应尽量减少其使用频率。

如需使用唤醒锁定,首先要将 WAKE_LOCK 权限添加到应用的清单文件中:

<uses-permission android:name="android.permission.WAKE_LOCK" />

如果您的应用包含使用服务来完成相关工作的广播接收器,您可以使用 WakefulBroadcastReceiver 管理唤醒锁定。这是首选方法。如果您的应用未采用该模式,您可以使用以下方法直接设置唤醒锁定:

    val wakeLock: PowerManager.WakeLock =
            (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
                newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
                    acquire()
                }
            }

要释放唤醒锁定,请调用 wakelock.release()。这会释放您对 CPU 的声明。请务必在应用结束对唤醒锁定的使用后立即将其释放,以避免消耗电池电量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无线局域网接入

非法接人是指非授权终端与无线局域网中的接入点&#xff08;Access Point&#xff0c;AP&#xff09;之间建立关联的过程&#xff0c;非法接入使得非授权终端可以与无线局域网中的授权终端交换数据&#xff0c;并可以通过AP访问网络资源。 非法登录是指非授权用户远程登录网络…

【JavaEE初阶】第八节.多线程(基础篇)阻塞队列(案例二)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、阻塞队列概论 1.1 阻塞队列的概念与作用 1.2 阻塞队列的应用场景 —— 生产者消费者模型 1.3 生产者消费者模型的好处 二、阻塞队列的实现 2.1 Java标准库里面…

java中常见API该如何使用?

目录 API(Application Programming Interface)概述 如何使用API&#xff1f; API(Application Programming Interface)概述 –应用程序编程接口 –编写一个机器人程序去控制机器人踢足球&#xff0c;程序就需要向机器人发出向前跑、向后跑、射门、抢球等各种命令&#xff0c…

SECURECRT全局日志文件设置

SecureCRT配置自动记录日志很多人用SecureCRT时&#xff0c;希望自动记录日志&#xff0c;一个是方便以后查阅&#xff0c;一个是对自己的操作有个记录。可以看看自己做了什么操作&#xff0c;有时甚至可以看看之前是不是犯了什么错&#xff0c;是个很不错的功能。设置很简单&a…

[数据库迁移]-ES集群的部署

[数据库迁移]-ES集群的部署 森格 | 2023年1月 上一篇文件我们已经把Linux系统的LVM逻辑卷完成了&#xff0c;那下面我们就该把es集群环境搭建起来了&#xff0c;主要是以shell脚本来进行一键部署。 上文回顾&#xff1a;[数据库迁移]-LVM逻辑卷管理 一、环境介绍 1.1 环境配…

微分先行PID控制算法及仿真

微分先行PID控制的结构如图1所示&#xff0c;其特点是只对输出量y(k)进行微分&#xff0c;而对给定值y(k)不作微分。这样&#xff0c;在改变给定值时&#xff0c;输出不会改变&#xff0c;而被控量的变化通常是比较缓和的。这种输出量先行微分控制适用于给定值yd(k)频繁升降的场…

isNotEmpty() 和 isNotBlank() 的区别,字符串判空, StringUtils工具包 StringUtil工具类

目录1.StringUtils 和 StringUtilStringUtils 的依赖&#xff1a;StringUtils 的用法&#xff1a;StringUtil 工具类2. isNotEmpty() 和 isNotBlank()1.StringUtils 和 StringUtil 注&#xff1a;StringUtils 和 StringUtil 的区别&#xff08;StringUtil为自定义工具类&#…

微信小程序——获取接口调用凭据access_token

问题背景 今天在搞一个微信小程序的时候&#xff0c;使用到了发布内容的问题&#xff0c;然后想使用微信请求接口&#xff0c;结果遇到了请求接口需要获取调用凭证access_token。 查看微信小程序开发文档 打开微信小程序开发文档后&#xff0c;打开服务端相关文档&#xff0…

电影《流浪地球2》观后感

趁着过年看了电影《流浪地球2》这部电影&#xff0c;同时也听说&#xff0c;今天好几部同期电影也不错&#xff0c;思考了一下&#xff0c;选择这部&#xff0c;记得以前有个笑话&#xff0c;说《流浪地球》成功开启了中国科幻的大门&#xff0c;而另一部电影成功将这部大门关起…

监听器、过滤器、拦截器,参考多篇文章整合而成,没有千篇一律的抽象回答!

参考文章1 过滤器、监听器、拦截器一、监听器二、过滤器1. POM.xml导包2. web.xml配置3. Filter过滤器编程三、拦截器1. 定义拦截器2. 配置加载拦截器3. 新建页面的存放地点四、过滤器、监听器、拦截器1. 三者作用2. 三者调用的时序&#xff08;监听器、过滤器、拦截器&#xf…

ESP32设备驱动-DS18B20温度传感器驱动

DS18B20温度传感器驱动 1、DS18B20驱动 DS18B20 是一款温度传感器,可用于各种简单的项目。 通过使用 One-Wire (I2C) 总线,可以将多个传感器连接到ESP32。 DS18B20 的成本也相对较低,只需要一个额外的 4k7 上拉电阻。在本文中,将创建一个基本示例,通过串行读取温度和输出…

java ssm酒店客房管理系统设计

目 录 1 绪 论 1 1.1 系统开发背景 1 1.2 开发意义 1 1.3 可行性分析 1 1.3.1 技术可行性分析 2 1.3.2 市场需求的可行性分析 2 1.4 国内外发展状况 2 1.5 本文中的章节介绍 2 2 开发技术简介 3 2.1 B/S简介 3 2.2 MYSQL的主要功能 3 …

竟然34%的孩子有腺样体肥大,保守还是手术?

腺样体肥大是一个常见的问题&#xff0c;患病率也越来越高。研究发现&#xff0c;34%的儿童和青少年患有这种疾病。腺样体肥大最容易导致鼻塞、口腔呼吸、儿童打鼾、睡眠影响等问题&#xff0c;严重会导致反复鼻窦炎、中耳炎、听力损伤&#xff0c;一些父母也知道它可能会导致孩…

idea插件restfultool和httpclient

restfultool使用测试controllerRestController RequestMapping("/api") Slf4j public class ApiController {GetMapping("test1")public String test1(RequestParam String msg) {return "hello world " msg;}GetMapping("test2/{msg}&quo…

【华为上机真题】密码要求

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

Pytorch——anaconda环境下安装GPU版本

首先查看自己 CUDA 搜索 点击 系统信息 可以看到&#xff0c;我的CUDA 是 11.1版本 随后上官网查找匹配的 pytorch 、torchvision、torchaudio 版本 Previous PyTorch Versions | PyTorch 然后直接对 .whl 文件进行下载 https://download.pytorch.org/whl/torch_stable.ht…

操作系统-进程管理

一、进程的组成和状态 1、操作系统的作用:通过资源管理提高计算机系统的效率改善人机界面向用户提供友好的工作环境。 2、操作系统的特征:并发性、共享性、虚拟性、不确定性。 3、操作系统的功能:进程管理、存储管理、文件管理、设备管理、作业管理。 4、操作系统的分类:批处理…

创业青年张继群:数智强农 现代农业加“数”前行

富贵险中求,也在险中丢,求时十之一,丢时十之九。 大丈夫行事,当摒弃侥幸之念,必取百炼成钢,厚积分秒之功,始得一鸣惊人。 央广网南宁1月11日消息(记者罗兰 通讯员张思颖)“经过此次比赛,在今后的推广中,我们会深入思考到底怎样才能把智慧农业做好,到底怎样才能真…

程序员为什么要写技术博客?都在哪些平台呢?

目录 一、程序员为什么要写技术博客&#xff1f; 1、真正掌握技术 2、没有人会那么在意你 3、珍惜时间 4、懒于思考&#xff0c;疏于总结 5、碎片化学习 6、优秀大神 7、更好的求职机会 8、努力的人一直都有 二、程序员都在哪些平台写技术博客呢&#xff1f; 1、git…

Python 采集免费代理,并检测其是否可用

本篇博客将采集互联网中公开代理 IP&#xff0c;并通过 IP 检测站点检测代理的可用性。 未来该内容将会被集成到 爬虫训练场 中。 &#x1f4b0;学习目录&#x1f4b0;Python 采集免费代理 IP模块准备与目标站点说明实战采集代理 IP 可用性检测对接站点为何使用代理 IP&#xf…