此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。
简介
大多数实际应用都需要执行长时间运行的后台任务。例如,应用可能会将文件上传到服务器、从服务器同步数据并将其保存到 Room
数据库、向服务器发送日志,或对数据执行代价高昂的操作。此类操作应在后台界面线程(主线程)之外执行。后台任务会消耗设备的有限资源,例如 RAM 和电池。如果处理不当,可能会导致用户体验不佳。
在此 Codelab 中,您将学习如何使用 WorkManager
以优化、高效的方式安排后台任务。如需详细了解 Android 中可用于后台处理的其他解决方案,请参阅后台处理指南。
您应当已掌握的内容
- 如何使用
ViewModel
、LiveData
和Room
Android 架构组件。 - 如何对
LiveData
类执行转换。 - 如何构建和启动协程。
- 如何在数据绑定中使用绑定适配器。
- 如何使用存储库模式加载缓存的数据。
学习内容
- 如何创建表示工作单元的
Worker
。 - 如何创建
WorkRequest
以请求执行工作。 - 如何为
WorkRequest
添加约束条件以定义工作器的运行方式和时间。 - 如何使用
WorkManager
调度后台任务。
您将执行的操作
- 创建一个工作器来执行后台任务,以便从网络中预提取 DevBytes 视频播放列表。
- 安排工作器定期运行。
- 为
WorkRequest
添加约束条件。 - 安排每天执行一次的定期
WorkRequest
。
在此 Codelab 中,您将开发在上一个 Codelab 中开发的 DevBytes 应用。(如果您没有此应用,可以下载本课程的起始代码)。
DevBytes 应用列出了 DevByte 视频,这些视频是由 Google Android 开发者关系团队制作的简短教程。这些视频介绍了 Android 开发方面的开发者功能和最佳做法。
每天预提取一次视频,以改善应用中的用户体验。这样可确保用户在打开应用时就能看到新鲜内容。
在此任务中,您将下载并检查起始代码。
第 1 步:下载并运行起始应用
您可以继续学习在之前的 Codelab 中构建的 DevBytes 应用(如果您有的话)。或者,您也可以下载起始应用。
在此任务中,您将下载并运行起始应用并检查起始代码。
- 如果您还没有 DevBytes 应用,请从 GitHub 的 DevBytesRepository 项目下载此 Codelab 的 DevBytes 起始代码。
- 解压缩代码并在 Android Studio 中打开项目。
- 将测试设备或模拟器连接到互联网(如果尚未连接)。构建并运行应用。应用会从网络中获取 DevByte 视频列表并显示这些视频。
- 在该应用中,点按任意视频,以在 YouTube 应用中打开。
第 2 步:浏览代码
起始应用附带上一个 Codelab 中引入的大量代码。本 Codelab 的起始代码包含网络、界面、离线缓存和代码库模块。您可以专注于使用 WorkManager
调度后台任务。
- 在 Android Studio 中,展开所有软件包。
- 探索
database
软件包。该软件包包含数据库实体和本地数据库(使用Room
实现)。 - 探索
repository
软件包。软件包中包含VideosRepository
类,该类可从应用的其余部分中提取数据层。 - 在上一个 Codelab 的帮助下,自行探索其余的起始代码。
WorkManager
是 Android 架构组件之一,也是 Android Jetpack 的一部分。WorkManager
适用于可延期且需要保证执行的后台工作:
- 可延期表示工作无需立即运行。例如,可以将分析数据发送到服务器或在后台同步数据库,这样做可以推迟。
- 保证执行意味着,即使应用退出或设备重启,任务也会运行。
在 WorkManager
运行后台工作时,它可以处理电池问题和系统运行状况方面的兼容性问题和最佳做法。WorkManager
可向后兼容至 API 级别 14。WorkManager
根据设备 API 级别选择后台任务的调度方式。它可能会使用 JobScheduler
(在 API 23 及更高版本中),或结合使用 AlarmManager
和 BroadcastReceiver
。
WorkManager
还可让您设置有关何时运行后台任务的条件。例如,您可能只想在电池状态、网络状态或充电状态满足特定条件时运行任务。此 Codelab 后面会介绍如何设置限制条件。
在此 Codelab 中,您可以安排一项任务,即每天从网络中预提取 DevBytes 视频播放列表一次。如需安排此任务,请使用 WorkManager
库。
- 打开
build.gradle (Module:app)
文件并将WorkManager
依赖项添加到项目中。
如果您使用该库的最新版本,解决方案应用应按预期编译。如果不能解决问题,请尝试解决问题,或恢复如下所示的库版本。
// WorkManager dependency
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
- 同步您的项目,并确保没有编译错误。
在向项目添加代码之前,请先熟悉 WorkManager
库中的以下类:
Worker
您可以在这个类中定义在后台运行的实际工作(任务)。您可以扩展此类并替换doWork()
方法。doWork()
方法用于放置要在后台执行的代码,例如与服务器同步数据或处理图片。在此任务中,您将实现Worker
。WorkRequest
这个类表示在后台运行工作器的请求。在Constraints
的帮助下,使用WorkRequest
配置如何以及何时运行工作器任务,例如插入设备或连接到 Wi-Fi。在后续任务中实现WorkRequest
。WorkManager
此课程安排并运行您的WorkRequest
。WorkManager
在安排工作请求时采用一种在系统资源上分散负载的方式,同时遵循您指定的约束条件。在后续任务中实现WorkManager
。
第 1 步:创建工作器
在此任务中,您将添加 Worker
以在后台预取 DevBytes 视频播放列表。
- 在
devbyteviewer
软件包内,创建一个名为work
的新软件包。 - 在
work
软件包内,新建一个名为RefreshDataWorker
的 Kotlin 类。 - 从
CoroutineWorker
类扩展RefreshDataWorker
类。传入context
和WorkerParameters
作为构造函数参数。
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
}
- 要解决抽象类错误,请替换
RefreshDataWorker
类中的doWork()
方法。
override suspend fun doWork(): Result {
return Result.success()
}
挂起函数是一种可以在以后暂停和恢复的函数。挂起函数可以执行一项长时间运行的操作并等待其完成,而不会阻塞主线程。
第 2 步:实现 doWork()
Worker
类中的 doWork()
方法在后台线程上调用。该方法会同步执行工作,应返回 ListenableWorker.Result
对象。Android 系统会给 Worker
最多 10 分钟的时间完成执行,并返回 ListenableWorker.Result
对象。此期限过后,系统会强行停止 Worker
。
要创建 ListenableWorker.Result
对象,请调用以下静态方法之一,指示后台工作的完成状态:
Result.success()
- 已成功完成工作。Result.failure()
- 已完成,且出现永久性失败。Result.retry()
- 工作出现暂时性故障,应重试。
在此任务中,您将实现 doWork()
方法以从网络获取 DevBytes 视频播放列表。您可以重复使用 VideosRepository
类中的现有方法,从网络检索数据。
- 在
RefreshDataWorker
类中的doWork()
内,创建并实例化一个VideosDatabase
对象和一个VideosRepository
对象。
override suspend fun doWork(): Result {
val database = getDatabase(applicationContext)
val repository = VideosRepository(database)
return Result.success()
}
- 在
RefreshDataWorker
类的doWork()
中的return
语句上方,调用try
块内的refreshVideos()
方法。添加日志以跟踪工作器的运行时间。
try {
repository.refreshVideos( )
Timber.d("Work request for sync is run")
} catch (e: HttpException) {
return Result.retry()
}
如需解决“未解析的引用”错误,请导入 retrofit2.HttpException
。
- 以下是完整的
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
类有两个具体实现:
OneTimeWorkRequest
类适用于一次性任务。(一次性任务仅发生一次。)PeriodicWorkRequest
类适用于按时间间隔重复的定期工作。
任务可以是一次性的,也可以是周期性的,因此请相应地选择类。如需详细了解如何安排周期性工作,请参阅周期性工作文档。
在此任务中,您将定义并安排一个 WorkRequest
以运行您在上一个任务中创建的工作器。
第 1 步:设置周期性工作
在 Android 应用中,Application
类是包含其他所有组件(例如 Activity 和服务)的基类。创建应用或进程进程后,Application
类(或 Application
的任何子类)都会先于任何其他类进行实例化。
在此示例应用中,DevByteApplication
类是 Application
类的子类。DevByteApplication
类非常适合调度 WorkManager
。
- 在
DevByteApplication
类中,创建一个名为setupRecurringWork()
的方法以设置周期性后台工作。
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
- 在
setupRecurringWork()
方法内,使用PeriodicWorkRequestBuilder()
方法创建并初始化定期运行的请求,每天运行一次。传入您在上一个任务中创建的RefreshDataWorker
类。传入时间间隔为TimeUnit.
DAYS
的重复1
。
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
.build()
如需解决此错误,请导入 java.util.concurrent.TimeUnit
。
第 2 步:使用 WorkManager 调度 WorkRequest
定义 WorkRequest
后,您可以使用 enqueueUniquePeriodicWork()
方法通过 WorkManager
对其进行调度。使用此方法,您可以将具有唯一名称的 PeriodicWorkRequest
添加到队列中,每次只能有一个特定名称的 PeriodicWorkRequest
处于活动状态。
例如,您可能只想执行一项同步操作。如果某个同步操作正在等待处理,您可以选择使用 ExistingPeriodicWorkPolicy 让该操作运行或将其替换为新工作。
如需详细了解 WorkRequest
调度方法,请参阅 WorkManager
文档。
- 在
RefreshDataWorker
类中,在该类的开头添加伴生对象。定义工作名称以唯一标识此工作器。
companion object {
const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
- 在
DevByteApplication
类中,在setupRecurringWork()
方法的末尾,使用enqueueUniquePeriodicWork()
方法调度工作。传入 ExistingPeriodicWorkPolicy 的KEEP
枚举。传入repeatingRequest
作为PeriodicWorkRequest
参数。
WorkManager.getInstance().enqueueUniquePeriodicWork(
RefreshDataWorker.WORK_NAME,
ExistingPeriodicWorkPolicy.KEEP,
repeatingRequest)
如果存在具有相同名称的待处理(未完成)工作,ExistingPeriodicWorkPolicy.
KEEP
参数可让 WorkManager
保留以前的定期工作并舍弃新的工作请求。
- 在
DevByteApplication
类的开头,创建一个CoroutineScope
对象。传入Dispatchers.Default
作为构造函数参数。
private val applicationScope = CoroutineScope(Dispatchers.Default)
- 在
DevByteApplication
类中,添加一个名为delayedInit()
的新方法来启动协程。
private fun delayedInit() {
applicationScope.launch {
}
}
- 在
delayedInit()
方法内,调用setupRecurringWork()
。 - 将 Timber 初始化从
onCreate()
方法移至delayedInit()
方法。
private fun delayedInit() {
applicationScope.launch {
Timber.plant(Timber.DebugTree())
setupRecurringWork()
}
}
- 在
DevByteApplication
类的onCreate()
方法的末尾,添加对delayedInit()
方法的调用。
override fun onCreate() {
super.onCreate()
delayedInit()
}
- 打开 Android Studio 窗口底部的 Logcat 窗格。按“
RefreshDataWorker
”过滤。 - 运行应用。
WorkManager
会立即安排您的周期性工作。
在 Logcat 窗格中,请注意显示已安排工作请求,然后成功运行的日志语句。
D/RefreshDataWorker: Work request for sync is run I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
系统会显示 WorkManager
库中的 WM-WorkerWrapper
日志,因此您无法更改此日志消息。
第 3 步:(可选)为 WorkRequest 安排最短时间间隔
在此步骤中,您将时间间隔从 1 天缩短为 15 分钟。这样做是为了查看定期工作请求的实际日志。
- 在
DevByteApplication
类的setupRecurringWork()
方法内,注释掉当前的repeatingRequest
定义。添加工作周期为15
分钟的新工作请求。
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
// .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
.build()
- 在 Android Studio 中打开 Logcat 窗格并按
RefreshDataWorker
进行过滤。如需清除之前的日志,请点击 Clear logcat 图标.
- 运行应用,
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
恭喜!您已创建工作器,并使用 WorkManager
安排了工作请求。但存在一个问题:您未指定任何限制条件。WorkManager
每天会调度 1 次工作,即使设备电量不足、进入休眠状态或未连接到网络时也是如此。这会影响设备的电池和性能,并可能导致用户体验不佳。
在下一个任务中,您将通过添加约束条件来解决此问题。
在上一个任务中,您使用 WorkManager
安排了工作请求。在此任务中,您将添加何时执行工作的条件。
定义 WorkRequest
时,您可以指定何时运行 Worker
的约束条件。例如,您可能希望指定工作应仅在设备处于空闲状态时运行,或仅在设备插入并连接到 Wi-Fi 时运行。您也可以为重试工作指定退避政策。支持的约束条件是 Constraints.Builder
中的 set 方法。如需了解详情,请参阅定义工作请求。
第 1 步:添加 Constraints 对象并设置一个约束条件
在此步骤中,您将创建一个 Constraints
对象,并针对该对象设置一个限制条件,即网络类型限制条件。(仅注意一个约束条件更容易获得日志。在后续步骤中,您需要添加其他限制条件。)
- 在
DevByteApplication
类中的setupRecurringWork()
开头,定义Constraints
类型的val
。使用Constraints.Builder()
方法。
val constraints = Constraints.Builder()
如需解决此错误,请导入 androidx.work.Constraints
。
- 使用
setRequiredNetworkType()
方法向constraints
对象添加网络类型约束条件。使用UNMETERED
枚举,使工作请求仅在设备使用的是不按流量计费的网络时运行。
.setRequiredNetworkType(NetworkType.UNMETERED)
- 使用
build()
方法从构建器生成约束条件。
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
现在,您需要将新创建的 Constraints
对象设置为工作请求。
- 在
DevByteApplication
类的setupRecurringWork()
方法内,将Constraints
对象设置为定期工作请求repeatingRequest
。要设置约束条件,请在build()
方法调用上方添加setConstraints()
方法。
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
.setConstraints(constraints)
.build()
第 2 步:运行应用并注意日志
在此步骤中,您将运行应用,并注意到后台中定时运行的受限工作请求。
- 如需取消之前安排的所有任务,请在设备或模拟器中卸载该应用。
- 在 Android Studio 中打开 Logcat 窗格。在 Logcat 窗格中,点击左侧的 Clear logcat 图标
以清除之前的日志。按“
work
”过滤。 - 您可以在设备或模拟器中关闭 Wi-Fi,以便了解约束条件的运作方式。当前代码仅设置一个限制条件,表示请求应仅在不按流量计费的网络上运行。由于 WLAN 处于关闭状态,设备未连接到网络(按流量计费或不按流量计费)。因此,不会满足此限制条件。
- 运行应用并注意 Logcat 窗格。
WorkManager
会立即安排后台任务。由于未满足网络约束条件,因此任务不会运行。
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
- 在设备或模拟器中开启 WLAN,然后查看 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
类中执行以下操作。
- 在
DevByteApplication
类的setupRecurringWork()
方法内,指明工作请求仅在电池电量不足时运行。在调用build()
方法之前添加约束条件,并使用setRequiresBatteryNotLow()
方法。
.setRequiresBatteryNotLow(true)
- 更新工作请求,使其仅在设备充电时运行。在调用
build()
方法之前添加约束条件,并使用setRequiresCharging()
方法。
.setRequiresCharging(true)
- 更新工作请求,使其仅在设备空闲时运行。在调用
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()
- 在
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)
}
- 如需移除之前安排的工作请求,请从设备或模拟器中卸载 DevBytes 应用。
- 运行应用,
WorkManager
会立即安排工作请求。工作请求每天运行一次,当所有约束条件都得到满足时。 - 只要应用安装,此工作请求就会在后台运行,即使应用未运行也是如此。因此,您应从手机中卸载该应用。
太棒了!您为 DevBytes 应用中的每日视频预取工作实现了并实现了电池供电的工作请求。WorkManager
将调度并运行工作,从而优化系统资源。用户及其电池将非常满意。
Android Studio 项目:DevBytesWorkManager。
- 借助
WorkManager
API,您可以轻松地调度必须可靠运行的可延期异步任务。 - 大多数实际应用都需要执行长时间运行的后台任务。如需以优化且高效的方式安排后台任务,请使用
WorkManager
。 WorkManager
库中的主要类是Worker
、WorkRequest
和WorkManager
。Worker
类代表一个工作单元。如需实现后台任务,请扩展Worker
类并替换doWork()
方法。WorkRequest
类表示执行工作单元的请求。WorkRequest
是为WorkManager
中调度的工作指定参数的基类。WorkRequest
类的具体实现有两种:OneTimeWorkRequest
用于一次性任务,PeriodicWorkRequest
用于定期工作请求。- 定义
WorkRequest
时,您可以指定Constraints
,指示Worker
应何时运行。限制条件包括设备是否已插入、设备是否处于空闲状态或者是否连接到 WLAN。 - 如需为
WorkRequest
添加约束条件,请使用Constraints.Builder
文档中列出的 set 方法。例如,如需指示WorkRequest
在设备电池电量不足时不应运行,请使用setRequiresBatteryNotLow()
set 方法。 - 定义
WorkRequest
后,请将任务移交给 Android 系统。为此,请使用WorkManager
enqueue
方法之一安排任务。 - 执行
Worker
的确切时间取决于WorkRequest
中使用的约束以及系统优化方式。按照这些限制,WorkManager
旨在提供最佳行为。
Udacity 课程:
Android 开发者文档:
其他:
此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:
- 根据需要布置作业。
- 告知学生如何提交家庭作业。
- 给家庭作业评分。
讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。
如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。
问题 1
WorkRequest
类的具体实现是什么?
▢ OneTimeWorkPeriodicRequest
▢ OneTimeWorkRequest
和 PeriodicWorkRequest
▢ OneTimeWorkRequest
和 RecurringWorkRequest
▢ OneTimeOffWorkRequest
和 RecurringWorkRequest
问题 2
WorkManager
使用以下哪个类在 API 23 及更高级别上调度后台任务?
▢ 仅 JobScheduler
▢ BroadcastReceiver
和 AlarmManager
▢ AlarmManager
和 JobScheduler
▢ Scheduler
和 BroadcastReceiver
问题 3
您使用哪个 API 来为 WorkRequest
添加约束条件?
▢ setConstraints()
▢ addConstraints()
▢ setConstraint()
▢ addConstraintsToWorkRequest()
开始学习下一课:
如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页。