Android Kotlin 基础知识 09.2:WorkManager

此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。

简介

大多数实际应用都需要执行长时间运行的后台任务。例如,应用可能会将文件上传到服务器、从服务器同步数据并将其保存到 Room 数据库、将日志发送到服务器,或者对数据执行开销较大的操作。此类操作应在后台(而非界面线程 [主线程])中执行。后台任务会使用设备的有限资源,例如 RAM 和电池电量。如果处理不当,可能会导致用户体验不佳。

在此 Codelab 中,您将学习如何使用 WorkManager 以经优化、高效的方式调度后台任务。如需详细了解 Android 中可用于后台处理的其他解决方案,请参阅后台处理指南

您应当已掌握的内容

  • 如何使用 ViewModelLiveDataRoom Android 架构组件。
  • 如何对 LiveData 类执行转换。
  • 如何构建和启动协程
  • 如何在数据绑定中使用绑定适配器
  • 如何使用存储库模式加载缓存的数据。

学习内容

  • 如何创建 Worker,表示一个工作单元。
  • 如何创建 WorkRequest 以请求执行工作。
  • 如何向 WorkRequest 添加约束条件,以定义 worker 的运行方式和时间。
  • 如何使用 WorkManager 安排后台任务。

您将执行的操作

  • 创建一个工作器,用于执行后台任务以从网络预提取 DevBytes 视频播放列表。
  • 安排工作器定期运行。
  • WorkRequest 添加约束条件。
  • 安排定期执行的 WorkRequest,每天执行一次。

在此 Codelab 中,您将使用在上一个 Codelab 中开发的 DevBytes 应用。(如果您没有此应用,可以下载本节课的起始代码。)

DevBytes 应用会显示一个 DevByte 视频列表,这些视频是由 Google Android 开发者关系团队制作的简短教程。这些视频介绍了 Android 开发的开发者功能和最佳实践。

您每天预提取一次视频,从而提升应用中的用户体验。这样可确保用户在打开应用后立即获得新内容。

在此任务中,您将下载并检查起始代码。

第 1 步:下载并运行起始应用

您可以继续使用在上一个 Codelab 中构建的 DevBytes 应用(如果您有)。或者,您也可以下载初始应用。

在此任务中,您将下载并运行起始应用,并检查起始代码。

  1. 如果您还没有 DevBytes 应用,请从 GitHub 的 DevBytesRepository 项目下载此 Codelab 的 DevBytes 起始代码。
  2. 解压缩代码,然后在 Android Studio 中打开项目。
  3. 将测试设备或模拟器连接到互联网(如果尚未连接)。构建并运行应用。该应用会从网络中提取 DevByte 视频列表并显示这些视频。
  4. 在应用中,点按任意视频即可在 YouTube 应用中打开该视频。

第 2 步:探索代码

起始应用附带了上一个 Codelab 中介绍的许多代码。本 Codelab 的起始代码包含网络、界面、离线缓存和代码库模块。您可以专注于使用 WorkManager 安排后台任务。

  1. 在 Android Studio 中,展开所有软件包。
  2. 探索 database 软件包。该软件包包含数据库实体和使用 Room 实现的本地数据库。
  3. 探索 repository 软件包。该软件包包含 VideosRepository 类,该类可从应用的其余部分中提取数据层。
  4. 在之前 Codelab 的帮助下,自行探索其余的起始代码。

WorkManagerAndroid 架构组件之一,也是 Android Jetpack 的一部分。WorkManager 适用于可延迟且需要保证执行的后台工作:

  • 可延迟表示工作不需要立即运行。例如,将分析数据发送到服务器或在后台同步数据库是可以延迟的工作。
  • 有保证的执行意味着即使应用退出或设备重启,任务也会运行。

WorkManager 运行后台工作时,它会处理兼容性问题,并遵循电池和系统健康度最佳实践。WorkManager 可向后兼容至 API 级别 14。WorkManager 会根据设备 API 级别选择安排后台任务的适当方式。它可能会使用 JobScheduler(在 API 23 及更高版本上)或 AlarmManagerBroadcastReceiver 的组合。

您还可以使用 WorkManager 设置后台任务的运行条件。例如,您可能希望仅当电池状态、网络状态或充电状态满足特定条件时才运行任务。您将在本 Codelab 的后面部分学习如何设置限制条件。

在此 Codelab 中,您将安排一项任务,以每天从网络中预提取一次 DevBytes 视频播放列表。如需安排此任务,请使用 WorkManager 库。

  1. 打开 build.gradle (Module:app) 文件,并将 WorkManager 依赖项添加到项目中。

    如果您使用该库的最新版本,解决方案应用应能按预期进行编译。如果不是,请尝试解决问题,或恢复到下方显示的库版本。
// WorkManager dependency
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
  1. 同步项目,并确保没有编译错误。

在向项目添加代码之前,请先熟悉 WorkManager 库中的以下类:

  • Worker
    您可以在此类中定义要在后台运行的实际工作(任务)。您需要扩展此类并替换 doWork() 方法。doWork() 方法用于放置要在后台执行的代码,例如与服务器同步数据或处理图片。您将在此任务中实现 Worker
  • WorkRequest
    此类表示在后台运行工作器的请求。使用 WorkRequest 配置运行工作器任务的方式和时间,并借助 Constraints(例如设备已插电或已连接到 Wi-Fi)来完成配置。您将在后面的任务中实现 WorkRequest
  • WorkManager
    此类会调度并运行您的 WorkRequestWorkManager 以一种在系统资源上分散负载的方式调度工作请求,同时遵循您指定的约束条件。您将在后面的任务中实现 WorkManager

第 1 步:创建工作者

在此任务中,您将添加一个 Worker 以在后台预提取 DevBytes 视频播放列表。

  1. devbyteviewer 软件包中,新建一个名为 work 的软件包。
  2. work 软件包中,创建一个名为 RefreshDataWorker 的新 Kotlin 类。
  3. CoroutineWorker 类扩展 RefreshDataWorker 类。传入 contextWorkerParameters 作为构造函数参数。
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}
  1. 如需解决抽象类错误,请替换 RefreshDataWorker 类中的 doWork() 方法。
override suspend fun doWork(): Result {
  return Result.success()
}

挂起函数是一种可以暂停并在稍后恢复的函数。挂起函数可以执行长时间运行的操作,并等待其完成,而不会阻塞主线程。

第 2 步:实现 doWork()

Worker 类中的 doWork() 方法是在后台线程上调用的。该方法会同步执行工作,并应返回一个 ListenableWorker.Result 对象。Android 系统最多允许 Worker 在 10 分钟内完成执行并返回 ListenableWorker.Result 对象。此时间过期后,系统会强制停止 Worker

如需创建 ListenableWorker.Result 对象,请调用以下静态方法之一来指示后台工作的完成状态:

在此任务中,您将实现 doWork() 方法,以从网络中提取 DevBytes 视频播放列表。您可以重复使用 VideosRepository 类中的现有方法从网络中检索数据。

  1. RefreshDataWorker 类中,在 doWork() 内,创建并实例化一个 VideosDatabase 对象和一个 VideosRepository 对象。
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)

   return Result.success()
}
  1. RefreshDataWorker 类中,在 doWork() 内的 return 语句上方,在 try 块内调用 refreshVideos() 方法。添加了一个日志来跟踪工作器的运行时间。
try {
   repository.refreshVideos( )
   Timber.d("Work request for sync is run")
   } catch (e: HttpException) {
   return Result.retry()
}

如需解决“Unresolved reference”错误,请导入 retrofit2.HttpException

  1. 以下是完整的 RefreshDataWorker 类,供您参考:
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {
       val database = getDatabase(applicationContext)
       val repository = VideosRepository(database)
       try {
           repository.refreshVideos()
       } catch (e: HttpException) {
           return Result.retry()
       }
       return Result.success()
   }
}

Worker 定义工作单元,WorkRequest 则定义工作的运行方式和时间。WorkRequest 类有两个具体实现:

任务可以是一次性的,也可以是周期性的,因此请相应地选择任务类别。如需详细了解如何调度周期性工作,请参阅周期性工作文档

在此任务中,您将定义并安排一个 WorkRequest 来运行您在上一个任务中创建的 worker。

第 1 步:设置周期性工作

在 Android 应用中,Application 类是包含所有其他组件(例如 activity 和服务)的基类。在创建应用或软件包的进程时,系统会在任何其他类之前实例化 Application 类(或 Application 的任何子类)。

在此示例应用中,DevByteApplication 类是 Application 类的子类。DevByteApplication 类是安排 WorkManager 的好地方。

  1. DevByteApplication 类中,创建一个名为 setupRecurringWork() 的方法来设置周期性后台工作。
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
  1. setupRecurringWork() 方法内,使用 PeriodicWorkRequestBuilder() 方法创建并初始化一个每天运行一次的周期性工作请求。传入您在上一个任务中创建的 RefreshDataWorker 类。传入重复间隔时间 1,时间单位为 TimeUnit.DAYS
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

如需解决此错误,请导入 java.util.concurrent.TimeUnit

第 2 步:使用 WorkManager 调度 WorkRequest

定义 WorkRequest 后,您可以使用 enqueueUniquePeriodicWork() 方法通过 WorkManager 来调度它。此方法可让您向队列添加具有唯一名称的 PeriodicWorkRequest,其中一次只能有一个具有特定名称的 PeriodicWorkRequest 处于活动状态。

例如,您可能只希望有一项同步操作处于活跃状态。如果有一个同步操作处于待处理状态,您可以选择让其运行,也可以使用 ExistingPeriodicWorkPolicy 将其替换为新工作。

如需详细了解如何安排 WorkRequest,请参阅 WorkManager 文档。

  1. RefreshDataWorker 类中,于类开头处添加一个伴生对象。定义一个工作名称,以唯一标识此 worker。
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. DevByteApplication 类中,在 setupRecurringWork() 方法的末尾,使用 enqueueUniquePeriodicWork() 方法安排工作。为 ExistingPeriodicWorkPolicy 传入 KEEP 枚举。传入 repeatingRequest 作为 PeriodicWorkRequest 参数。
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

如果存在同名的待处理(未完成)工作,ExistingPeriodicWorkPolicy.KEEP 参数会使 WorkManager 保留之前的周期性工作并舍弃新的工作请求。

  1. DevByteApplication 类的开头,创建一个 CoroutineScope 对象。传入 Dispatchers.Default 作为构造函数参数。
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. DevByteApplication 类中,添加一个名为 delayedInit() 的新方法以启动协程。
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. delayedInit() 方法内,调用 setupRecurringWork()
  2. 将 Timber 初始化从 onCreate() 方法移至 delayedInit() 方法。
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. DevByteApplication 类中,在 onCreate() 方法的末尾,添加对 delayedInit() 方法的调用。
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. 打开 Android Studio 窗口底部的 Logcat 窗格。过滤条件:RefreshDataWorker
  2. 运行应用。WorkManager 会立即安排您的周期性工作。

    Logcat 窗格中,注意显示工作请求已安排并成功运行的日志语句。
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

WM-WorkerWrapper 日志是从 WorkManager 库中显示的,因此您无法更改此日志消息。

第 3 步:(可选)安排 WorkRequest 以最小间隔运行

在此步骤中,您将时间间隔从 1 天缩短到 15 分钟。这样做是为了查看定期工作请求的实际日志。

  1. DevByteApplication 类中,在 setupRecurringWork() 方法内,注释掉当前的 repeatingRequest 定义。添加一个周期性重复间隔为 15 分钟的新工作请求。
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. 在 Android Studio 中打开 Logcat 窗格,并按 RefreshDataWorker 进行过滤。如需清除之前的日志,请点击 Clear logcat 图标
  2. 运行应用,WorkManager 会立即安排重复性工作。在 Logcat 窗格中,注意日志 - 工作请求每 15 分钟运行一次。等待 15 分钟,查看另一组工作请求日志。您可以让应用保持运行状态,也可以将其关闭;工作管理器应仍会运行。

    请注意,间隔有时不到 15 分钟,有时超过 15 分钟。(确切时间取决于操作系统电池优化。)
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work
 

恭喜!您创建了一个 worker,并使用 WorkManager 调度了工作请求。但有一个问题:您未指定任何限制。WorkManager 会每天安排一次工作,即使设备电池电量不足、处于休眠状态或没有网络连接也是如此。这会影响设备电池和性能,并可能导致糟糕的用户体验。

在下一个任务中,您将通过添加限制来解决此问题。

在上一个任务中,您使用 WorkManager 安排了工作请求。在此任务中,您将添加用于指定何时执行工作的条件。

定义 WorkRequest 时,您可以指定 Worker 的运行时间限制。例如,您可能希望指定工作应仅在设备空闲时运行,或者仅在设备已插电并连接到 Wi-Fi 时运行。您还可以指定用于重试工作的退避政策。支持的限制条件Constraints.Builder 中的设置方法。如需了解详情,请参阅定义工作请求

第 1 1 步:添加 Constraints 对象并设置一个约束条件

在此步骤中,您将创建一个 Constraints 对象,并为该对象设置一个限制条件(即网络类型限制条件)。(如果只有一个限制条件,则更容易注意到日志。在后续步骤中,您将添加其他限制条件。)

  1. DevByteApplication 类中,在 setupRecurringWork() 的开头,定义一个类型为 Constraintsval。使用 Constraints.Builder() 方法。
val constraints = Constraints.Builder()

如需解决此错误,请导入 androidx.work.Constraints

  1. 使用 setRequiredNetworkType() 方法向 constraints 对象添加网络类型限制。使用 UNMETERED 枚举,以便工作请求仅在设备连接到不按流量计费的网络时运行。
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. 使用 build() 方法从构建器生成约束条件。
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

现在,您需要将新创建的 Constraints 对象设置为工作请求。

  1. DevByteApplication 类中,在 setupRecurringWork() 方法内,将 Constraints 对象设置为周期性工作请求 repeatingRequest。如需设置限制条件,请在 build() 方法调用上方添加 setConstraints() 方法。
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

第 2 步:运行应用并注意日志

在此步骤中,您将运行应用,并注意到受限工作请求正在后台以一定的时间间隔运行。

  1. 从设备或模拟器中卸载应用,以取消之前排定的所有任务。
  2. 在 Android Studio 中打开 Logcat 窗格。在 Logcat 窗格中,点击左侧的清除 Logcat 图标,清除之前的日志。过滤条件:work
  3. 关闭设备或模拟器中的 Wi-Fi,以便了解约束条件的工作方式。当前代码仅设置了一个限制条件,即请求应仅在非按流量计费的网络上运行。由于 Wi-Fi 已关闭,设备未连接到任何网络(按流量计费或不按流量计费)。因此,此限制条件将无法满足。
  4. 运行应用,然后注意 Logcat 窗格。WorkManager 会立即安排后台任务。由于未满足网络限制,因此未运行该任务。
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. 在设备或模拟器中开启 Wi-Fi,然后观察 Logcat 窗格。现在,只要满足网络限制条件,调度的后台任务就会大约每 15 分钟运行一次。
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

第 3 步:添加更多限制条件

在此步骤中,您将向 PeriodicWorkRequest 添加以下限制条件:

  • 电池电量不低。
  • 设备正在充电。
  • 设备处于空闲状态;仅适用于 API 级别 23 (Android M) 及更高版本。

DevByteApplication 类中实现以下各项。

  1. DevByteApplication 类中的 setupRecurringWork() 方法内,指明工作请求应仅在电池电量不低时运行。在 build() 方法调用之前添加限制,并使用 setRequiresBatteryNotLow() 方法。
.setRequiresBatteryNotLow(true)
  1. 更新工作请求,使其仅在设备充电时运行。在 build() 方法调用之前添加限制,并使用 setRequiresCharging() 方法。
.setRequiresCharging(true)
  1. 更新工作请求,使其仅在设备处于空闲状态时运行。在 build() 方法调用之前添加限制,并使用 setRequiresDeviceIdle() 方法。此约束条件仅在用户未主动使用设备时运行工作请求。此功能仅适用于 Android 6.0 (Marshmallow) 及更高版本,因此请添加 SDK 版本 M 及更高版本的条件。
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

以下是 constraints 对象的完整定义。

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. setupRecurringWork() 方法内,将请求间隔改回每天一次。
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

以下是 setupRecurringWork() 方法的完整实现,其中包含一个日志,以便您跟踪何时安排了周期性工作请求。

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. 如需移除之前安排的工作请求,请从设备或模拟器中卸载 DevBytes 应用。
  2. 运行应用,WorkManager 会立即调度工作请求。工作请求每天运行一次,前提是满足所有约束条件。
  3. 只要应用已安装,即使未运行,此工作请求也会在后台运行。因此,您应从手机中卸载该应用。

太棒了!您为 DevBytes 应用中视频的每日预提取实现了并安排了省电的工作请求。WorkManager 将安排并运行该工作,从而优化系统资源。您的用户及其电池会非常满意。

Android Studio 项目:DevBytesWorkManager

  • 使用 WorkManager API 可以轻松调度必须可靠运行的可延期异步任务。
  • 大多数实际应用都需要执行长时间运行的后台任务。如需以经优化、高效的方式调度后台任务,请使用 WorkManager
  • WorkManager 库中的主要类为 WorkerWorkRequestWorkManager
  • Worker 类表示工作单元。如需实现后台任务,请扩展 Worker 类并替换 doWork() 方法。
  • WorkRequest 类表示执行工作单元的请求。WorkRequest 是用于指定在 WorkManager 中安排的工作的参数的基类。
  • WorkRequest 类有两个具体实现:OneTimeWorkRequest 用于一次性任务,PeriodicWorkRequest 用于周期性工作请求。
  • 在定义 WorkRequest 时,您可以指定 Constraints 来指明 Worker 应何时运行。限制条件包括设备是否已接通电源、设备是否处于空闲状态,以及是否已连接到 Wi-Fi 等。
  • 如需向 WorkRequest 添加限制,请使用Constraints.Builder 文档中列出的设置方法。例如,如需指明当设备电池电量不足时,不应运行 WorkRequest,请使用 setRequiresBatteryNotLow() 设置方法。
  • 定义 WorkRequest 后,将任务提交给 Android 系统。为此,请使用 WorkManager enqueue 方法安排任务。
  • Worker 的确切执行时间取决于 WorkRequest 中使用的约束以及系统优化。WorkManager 经过专门设计,能够在满足这些约束的情况下提供可能的最佳行为。

Udacity 课程:

Android 开发者文档:

其他:

此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:

  • 根据需要布置作业。
  • 告知学生如何提交家庭作业。
  • 给家庭作业评分。

讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。

如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。

问题 1

WorkRequest 类的具体实现是什么?

OneTimeWorkPeriodicRequest

OneTimeWorkRequestPeriodicWorkRequest

OneTimeWorkRequestRecurringWorkRequest

OneTimeOffWorkRequestRecurringWorkRequest

问题 2

WorkManager 使用以下哪个类在运行 API 23 及更高级别的设备上调度后台任务?

▢ 仅限 JobScheduler

BroadcastReceiverAlarmManager

AlarmManagerJobScheduler

SchedulerBroadcastReceiver

问题 3

使用哪个 API 向 WorkRequest 添加约束条件?

setConstraints()

addConstraints()

setConstraint()

addConstraintsToWorkRequest()

开始学习下一课:10.1 样式和主题

如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页