随着设施性能晋升和软件生态倒退,越来越多的 Android 利用须要执行绝对更简单的网络、异步和离线等工作。例如用户想要离线观看某个视频,又不想始终停留在利用界面期待下载实现,那么就须要以肯定的形式让这些离线的过程在后盾运行。再比方您想将一段精彩的 Vlog 分享到社交媒体,必定也会心愿视频上传时不会影响到本人持续应用设施。这就波及到了咱们明天分享的主题: 应用 WorkManager 治理后盾和前台工作。

如果您更喜爱通过视频理解此内容,请在此处查看:

https://www.bilibili.com/vide...

△ 古代 WorkManager API 已公布

本文将着重探讨 WorkManager 的 API 以及用法,帮忙您深刻理解它的运行机制,以及在理论开发中的应用形式。近期也将会有另一篇对于在 Android Studio 中如何更好地应用 WorkManager 的文章,敬请关注。

WorkManager 根底 API

从首个稳固版本公布以来,WorkManager 提供了一些根底 API,帮忙您定义工作、放入队列、顺次执行,且在工作实现时告诉您的利用。以性能划分分类,这些根底 API 包含:

提早执行

最后的版本中,这些工作只能被定义为提早执行,也就是它们会在定义之后延期再开始执行。通过这种延期执行策略,一些不紧急或优先级不高的工作将会推后执行。

WorkManager 的延期执行会充分考虑设施的低电耗状态,以及利用的待机存储分区,因而您不用思考工作须要在哪个具体工夫被执行,这些都交给 WorkManager 思考即可。

工作束缚

WorkManager 反对对给定工作运行设定约束条件,束缚 可确保将工作提早到满足最佳条件时运行。例如,仅在设施采纳不按流量计费的网络连接时、当设施处于闲暇状态或者有足够的电量时运行。您能够分心开发利用的其余性能,将对工作条件的查看交给 WorkManager。

工作间的依赖关系

咱们晓得,工作之间是可能存在依赖关系的。比方您正在开发一个视频编辑利用,当剪辑实现后用户可能须要分享到社交媒体,于是您的利用须要顺次渲染若干个视频片段,而后将它们一起上传到视频服务。这个过程是具备先后秩序的,也就是上传工作依赖渲染工作的实现。

再举另外一个例子,当您的利用实现与后端同步数据后,兴许您心愿同步过程中产生的本地日志文件被及时清理,或者是将来自后端的新数据填充到本地数据库中。于是您能够申请 WorkManager 依照程序或者并行执行这些工作,从而实现各个工作之间无缝连接。而 WorkManager 会在确保所有给定条件都满足后再运行后续的 Worker。

屡次执行的工作

很多具备与服务器同步性能的利用都具备这样的特点: 利用与后端服务器的同步往往不是一次性的,它可能是须要屡次执行的。比方当您的利用提供在线编辑服务时,肯定须要频繁将本地的编辑数据同步到云端,这就产生了定期执行的工作。

工作状态

因为您能够随时查看某个工作的状态,因而对于定期执行的工作而言,整个生命周期是通明的。您能够晓得一个工作是处于队列期待、运行中、阻塞还是已实现状态。

WorkManager 古代 API

上述的根底 API 早在咱们公布 WorkManager 的第一个稳定版时就曾经提供了。首次在 Android 开发者峰会中谈到 WorkManager 时,咱们把它看作是治理可延期后盾工作的一个库。现在从底层的角度来看,这种观点依然是成立的。但起初咱们又增加了更多新性能,并让 API 更合乎古代标准。

立刻执行

当初,当您的利用处于前台时,您能够申请立刻执行某项工作。随后即使利用被置于后盾,这项工作也不会被中断,而是持续进行。所以,即便用户切换到别的利用去应用,您的利用依然能够持续实现为照片增加滤镜、保留到本地、上传等一系列工作。

对于大型利用的开发商来说,他们须要在优化资源应用方面投入更多的资源和精力。但 WorkManager 能够凭借优良的资源分配策略大大加重他们的累赘。

多过程 API

因为应用了新的多过程库解决工作,WorkManager 引入了新的 API,并进行了底层优化来帮忙大型利用更无效地安顿和执行工作。这得益于新的 WorkManager 能够在一个独立的过程中更高效地进行调度和解决。

强化的工作测试 API

利用公布到商店或是分发给用户之前,测试是十分重要的一个环节。因而咱们减少了 API 来帮忙您测试独自的 Worker 或是一组具备依赖关系的 Worker。

工具改良

在公布库的同时,咱们还改良了泛滥开发者工具。作为开发者,您能够间接应用 Android Studio 来拜访详尽的调试日志和查看信息。

开始应用 WorkManager

这些新引入的 API 和改良的工具在为开发者提供更大便当的同时,也促使咱们从新思考应用 WorkManager 的最佳时机。尽管从技术角度,咱们设计 WorkManager 的核心思想依然是正确的,但对于日益简单的开发生态而言,WorkManager 的能力曾经大大超过咱们的设计预期。

工作的 "长久化" 个性

WorkManager 能够解决您指派给它的任何类型的工作,因而它曾经进化成了一个专门解决工作且值得信赖的好工具。WorkManager 在全局作用域中执行您定义的 Worker,这意味着只有您的利用还在运行,不论是设施方向的变动,还是 Activity 被回收等,您的工作会被始终留存。不过单凭这一点,还不能称之领有 "长久化" 个性,因而 WorkManager 在底层还应用了 Room 数据库来保障当过程被完结或设施重启后,您的工作依然能够执行,并有可能从中断地位继续执行。

执行须要长时间运行的工作

WorkManager 2.3 版本引入了对长时间运行的工作的反对。当咱们谈到长时间运行的工作时,指的是运行工夫超过 10 分钟执行窗口期的工作。通常状况下,一个 Worker 的执行窗口期被限定为 10 分钟。为了能实现长时间运行的工作,WorkManager 将 Worker 的生命周期与前台服务的生命周期捆绑在一起。JobScheduler 和过程内调度程序 (In-Process Scheduler) 依然能感知到这种工作的存在。

因为前台服务把握着工作执行的生命周期,而前台服务又须要向用户展现告诉信息,所以咱们向 WorkManager 增加了相干的 API。用户的注意力持续时间是无限的,所以 WorkManager 提供了 API 让用户能不便地通过告诉进行长时间运行的工作。咱们来剖析一个长时间运行工作示例,代码如下:

class DownloadWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {    fun notification(progress: String): Notification = TODO()    // notification 办法依据进度信息生成一条 Android 告诉音讯。    suspend fun download(inputUrl: String,      outputFile: String,      callback: suspend (progress: String) -> Unit) = TODO()    // 定义一个用于分块下载的办法    fun createForegroundInfo(progress: String): ForegroundInfo {      return ForegroundInfo(id, notification(progress))    }     override suspend fun doWork(): Result {      download(inputUrl, outputFile) { progress ->         val progress = "Progress $progress %"        setForeground(createForegroundInfo(progress))      } // 提供了一个 suspend 标记的 doWork 办法,其中调用下载办法,并显示最新进度信息。      return Result.success()     } //下载实现后,Worker 只须要返回胜利即可}

△ DownloadWorker 类

这里有一个 DownloadWorker 类,它扩大自 CoroutineWorker 类。咱们会在这个类当中定义一些辅助办法来简化咱们的工作。首先是一个 notification 办法,它能够依据所给定的进度信息生成一条 Android 告诉音讯。接下来咱们要定义一个用于分块下载的办法,这个办法承受三个参数: 下载文件的 URL、文件保留的本地地位、suspend 回调函数。每当某个分块下载状态变动时,此回调就会被执行一次。于是,回调中携带的信息就能够被用来生成一条告诉。

有了这些辅助办法,咱们就能够将 WorkManager 执行长时间运行工作所须要的 ForegroundInfo 实例保存起来。ForegroundInfo 是由告诉 ID 和告诉实例组合结构而成的,请持续参照上述 CoroutineWorker 类的代码示例。

在这段代码里,咱们提供了一个 suspend 标记的 doWork 办法,其中调用了方才提到的分块下载辅助办法。因为每次回调产生时都会提供一些最新的进度信息,所以咱们能够利用这些信息来构建告诉,并调用 setForeground 办法来向用户显示这些告诉。这里调用 setForeground 的操作正是导致 Worker 长时间运行的起因。下载实现后,Worker 只须要返回胜利即可,随后 WorkManager 会将 Worker 的执行与前台服务解耦拆散、清理告诉音讯,并在必要时完结相干的服务。因而咱们的 Worker 自身并不需要执行服务管理工作。

终止已提交执行的工作

用户可能会忽然扭转主见,比方想要勾销某个工作。某个前台运行服务的告诉是无奈简略滑动勾销的,此前的做法是为这条告诉音讯增加一个动作,当用户点击时会向 WorkManager 发送一个信号,从而依照用户的用意终止某项工作。您也能够通过执行加急工作来终止,详见后文。

fun notification(progress: String): Notification {  val intent = WorkManager.getInstance(context)      .createCancelPendingIntent(getId())  return NotificationCompat.Builder(applicationContext, id)      .setContentTitle(title)      .setContentText(progress)      // 其余一些操作      .addAction(android.R.drawable.ic_delete, cancel, intent)      .build()}

△ 派生自 CoroutineWorker 类的 DownloadWorker 类

首先须要创立一个待处理的 Intent,它能够很不便地勾销某项工作。咱们须要调用 getId 办法来获取这个工作创立时的工作申请 ID,而后调用 createCancelPendingIntent API 创立这个 Intent 实例。当此 Intent 被触发时,它会向 WorkManager 发送勾销工作的信号,从而实现勾销工作的目标。

接下来就要生成带有自定义动作的告诉音讯了。咱们应用 NotificationCompat.Builder 设置告诉的题目,而后增加一些文字说明。随后调用 addAction 办法将告诉中的 "勾销" 按钮与上一步创立的 Intent 关联起来。于是,当用户点击 "勾销" 按钮时,这个 Intent 就会被发送到以后正在执行这个 Worker 的前台服务,从而将其终止。

执行加急工作

Android 12 中引入了新的前台服务限度,当利用在后盾时是无奈启动前台服务的。因而从 Android 12 开始,调用 setForegroundAsync 办法会抛出 Foreground Service Start Not Allowed Exception (不容许启动前台服务) 异样。这种状况下,WorkManager 就派上用场了。WorkManager 2.7 版本中减少了对加急工作 (expedited work) 的反对,所以接下来将会向您介绍如何应用 WorkManager 实现终止已提交执行的工作。

从用户的角度来说,加急工作是由用户发动的,因而对用户而言十分重要。甚至利用不在前台时,这些工作也须要被启动执行。比方聊天利用须要下载一条音讯中的附件,或者利用须要解决付款订阅的流程。在早于 Android 12 的 API 版本中,加急工作都是由前台服务执行的,而从 Android 12 开始,它们将由加急作业 (expedited job) 实现。

零碎以配额的模式限度了加急工作的数量。当利用处于前台时,加急工作不存在任何配额限度,然而当利用转到后盾运行时,就必须听从这些限度。配额的大小取决于利用的待机存储分区和过程重要性 (如优先级)。从字面意思来看,加急工作就是须要尽快启动执行的工作,这意味着此类工作对于提早相当敏感,所以也就不反对设定初始提早或是定期执行的设置。因为受到配额限度,加急工作也不能够取代长时间运行的工作。当您的用户想要发送一条重要信息时,WorkManager 会尽可能保障这条音讯尽快发送。

class SendMessageWorker(context: Context, parameters: WorkerParameters):   CoroutineWorker(context, parameters) {  override suspend fun getForegroundInfo(): ForegroundInfo {    TODO()  }      override suspend fun doWork(): Result {    TODO()  }}

△ 加急工作示例代码

例如,一个同步聊天利用音讯的案例应用了加急工作 API。SendMessageWorker 类扩大自 CoroutineWorker,而它的作用是负责从后盾为聊天利用同步音讯。加急工作须要在某个前台服务的上下文中运行,这很相似于 Android 12 之前版本中的长时间运行的工作。因而咱们的 Worker 类还须要实现 getForegroundInfo 接口,不便生成和显示告诉音讯。然而在 Android 12 上 WorkManager 不会显示其余的告诉,这是因为咱们定义的 Worker 背地是由加急作业实现的。您须要像平时那样实现一个 suspend 标记的 doWork 办法。须要留神的是,当您的利用占用了全副的配额后,加急作业可能会被中断。因而咱们的 Worker 最好能跟踪某些状态,以便在重新安排执行工夫后可能复原运行。

val request = OneTimeWorkRequestBuilder<ForegroundWorker>()    .setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST)    .build() WorkManager.getInstance(context)    .enqueue(request)

△ setExpedited API 示例代码

您能够应用 setExpedited API 来安顿加急工作,这个 API 会通知 WorkManager,用户认为给定的工作申请十分重要。因为所能安顿的工作存在配额限度,所以您须要表明当利用的配额用尽时该怎么解决,有两种备选计划: 其一是将加急申请变成惯例工作申请,其二是在配额耗尽时放弃新的工作申请。

WorkManager 多过程 API

从 2.5 版本开始,WorkManager 对反对多过程的利用进行了若干项改良。如果您须要应用多过程 API,就须要定义 work-multiprocess 工件的依赖项,多过程 API 的指标是在辅助过程中对 WorkManager 的冗余局部或高开销局部进行大范畴初始化操作。比方有多个过程在同时获取对立底层 SQLite 数据库的事务锁,这时就会产生 SQLite 争用;而这种争用正是咱们想要通过多过程 API 缩小的。另一方面,咱们还想确保过程内调度程序在正确的过程中运行。

为理解 WorkManager 初始化时哪些局部是冗余的,咱们须要分明它会在后盾执行哪些操作。

单过程的初始化

△ 单过程的初始化过程

首先察看一下单过程初始化过程。利用启动后,第一件事是有平台调用 Application.onCreate 办法。随后在过程生命周期的某个工夫点,WorkManager.getInstance 会被调用以启动 WorkManager 的初始化。当 WorkManager 初始化结束后,咱们运行 ForceStopRunnable。这个过程很重要,因为此时 WorkManager 会查看利用之前是否被强制进行过,它会比拟 WorkManager 存储的信息与 JobScheduler 或 AlarmManager 中的信息,确保作业都被精确编入执行打算中。同时,咱们也能够重新安排此前中断的某些工作,比方过程解体后进行的一些复原工作。大家都晓得,这样做的开销十分高,咱们须要在多个子系统中比拟和协调状态,然而现实状态下,这种操作只应该被执行一次。另外须要留神,过程内调度程序只在默认过程中运行。

多过程的初始化

△ 多过程的初始化过程

接着咱们再看看如果利用有第二个过程会产生什么。如果利用有第二个过程,基本上它会反复在第一个过程中实现的各项操作。首先第一个过程如上文那样初始化,并且因为这是主过程 (primary process),所以过程内调度程序 (In-Process Scheduler) 也会在其中运行。对于第二个过程,咱们会反复方才的过程,再次调用 Application.onCreate,而后从新初始化 WorkManager。这意味着,咱们将反复在第一个过程中所做的所有工作。

依据后面的剖析,您兴许会感到纳闷,为什么还须要再次执行 ForceStopRunable 呢?这是因为 WorkManager 并不知道这些过程中哪一个优先级较高。如果利用是屏幕键盘或者微件 (Widget),那么主过程可能并不等同于默认过程。另外,辅助过程 (secondary processes) 中也没有运行过程内调度程序 (因为它不是默认过程)。其实过程内调度程序所在的过程抉择十分重要,因为它不受其余持久性调度器的限度影响,所以调整其所在的过程能够显著晋升数据吞吐量。例如,JobScheduler 的作业下限是 100 个,而过程内调度程序则没有这个限度。

val config = Configuration.Builder()    .setDefaultProcessName("com.example.app")    .build()

△ 指定利用的默认过程示例代码

通过 WorkManager 定义主过程

咱们来看看如何定义指定的默认过程。首先依据本人的志愿设置默认过程的名称,这通常是利用的软件包名称,一旦定义了利用的默认过程,那么过程内调度程序就会在其中运行。然而辅助过程怎么办?有没有方法可能避免在其中再次初始化 WorkManager?事实证明这是能够办到的。其实咱们真正须要的是齐全不用初始化 WorkManager。

为了实现这个指标,咱们引入了 RemoteWorkManager。这个类须要绑定到指定过程 (主过程),并应用绑定服务将主要过程的所有工作申请转发到这个指定的主过程。这样一来,您就能够完全避免所有方才提到的跨过程 SQLite 争用,因为从开始到完结只有惟一一个过程在向底层 SQLite 数据库写入数据。您能够跟平常一样在辅助过程中创立工作申请,然而此处应该应用 RemoteWorkManager 而不是 WorkManager。应用 RemoteWorkManager 后,会通过绑定服务绑定到主过程中,并将所有工作申请进行转发,而后存储到特定队列期待执行。您能够通过将 RemoteWorkManager 服务合并到利用的 Android Manifest RXML 中实现这个绑定。

val request = OneTimeWorkRequestBuilder<DownloadWorker>()    .build() RemoteWorkManager.getInstance(context)    .enqueue(request)

△ 应用 RemoteWorkManager 示例代码

<!-- AndroidManifest.xml --><service    android:name="androidx.work.multiprocess.RemoteWorkManagerService"    android:exported="false" />

△ Manifest 注册服务示例代码

不同过程中运行 Worker

咱们曾经理解如何通过 WorkManager 定义主过程来防止争用,但有时候,您也心愿可能在不同的过程中运行 Worker。举个例子,如果您在某利用的辅助过程中运行机器学习工作流 (ML Pipeline),而且该利用还有专门的界面过程,那么您可能须要在不同的过程中运行不同的 Worker。比方在辅助过程中隔离执行某个工作,这样一来即便这个过程内呈现谬误而解体也不会导致利用的其余局部瘫痪而整体退出,尤其是要保障界面过程失常工作。要实现在不同过程中执行 Worker,您须要扩大 RemoteCoroutineWorker 类。这个类与 CoroutineWorker 相似,扩大之后您须要本人实现 doRemoteWork 接口。

public class IndexingWorker(  context: Context,  parameters: WorkerParameters): RemoteCoroutineWorker(context, parameters) {  override suspend fun doRemoteWork(): Result {    doSomething()    return Result.success()  }}

△ IndexingWorker 类示例代码

因为这个办法是在辅助过程中执行的,咱们依然要定义 Worker 须要与哪个过程绑定。为此,咱们还须要在 Android Manifest RXML 中增加一个条目。一个利用能够定义多项 RemoteWorker 服务,每项服务都在独立的过程中运行。

<!-- AndroidManifest.xml --><service    android:name="androidx.work.multiprocess.RemoteWorkerService"    android:exported="false"    android:process=":background" />

△ Manifest 注册服务示例代码

这里您能够看到,咱们为名为 background 的辅助过程增加了新服务。当初,您曾经在 RXML 中定义好了服务,还须要进一步在工作申请中指明要绑定的组件名称。

val inputData = workDataOf(  ARGUMENT_PACKAGE_NAME to context.packageName,  ARGUMENT_CLASS_NAME to RemoteWorkerService::class.java.name) val request = OneTimeWorkRequestBuilder<RemoteDownloadWorker>()    .setInputData(inputData)    .build() WorkManager.getInstance(context).enqueue(request)

△ 将 RemoteWork 对象放入队列示例代码

组件名称是软件包名和类名的组合,您须要将其增加到工作申请的输出数据中,而后用这个输出数据创立工作申请,这样一来 WorkManager 就晓得要绑定哪项服务了。咱们照常将工作放入队列中,当 WorkManager 筹备执行这项工作时,它首先依据输出数据中定义的内容找到绑定的服务,并执行 doRemoteWork 办法。这样一来,所有简单繁琐的跨过程通信的工作都交给 WorkManager 来解决了。

总结

WorkManager 是应答长执行工夫工作的举荐计划,举荐您应用 WorkManager 实现申请和勾销长时间运行的工作工作。通过本文理解到如何以及何时应用加急工作 API,如何编写牢靠的高性能多过程利用。心愿这篇文章对您有所帮忙,下一篇文章将对新的后台任务查看器做出简略介绍,敬请关注!

如需更多资源,请参阅:

  • Codelab: 应用 WorkManager 解决后台任务
  • Codelab: WorkManager 进阶常识
  • WorkManager 示例代码

欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!