1. 开始之前
本 Codelab 涵盖了 WorkManager,这是一个向后兼容、灵活且简单的库,用于执行可延迟的后台任务。 WorkManager
是 Android 上推荐用于执行可延迟任务的任务调度程序,它保证任务会执行。
先决条件
- 了解 StateFlow 和 ViewModel。如果您不熟悉这些类,请查看 ViewModel 和 Compose 中的状态 Codelab(特别是关于 ViewModel 和状态的部分)或 使用 Room Codelab 读取和更新数据(特别是关于 Flow 和 StateFlow 的部分)。
- 了解代码库和依赖注入。如需复习,请查看 添加代码库和手动依赖注入。
- 能够在您的应用中实现协程。
您将学习的内容
- 如何 将 WorkManager 添加到您的项目。
- 如何 安排一个简单的任务。
- 如何配置 工作器的输入和输出参数。
- 如何 链接工作器。
您将执行的操作
- 修改启动应用以使用 WorkManager。
- 实现一个工作请求来模糊图像。
- 通过链接工作来实现一系列串行工作。
- 将数据传入和传出正在调度的任务。
您需要的内容
- 最新稳定版本的 Android Studio
- 互联网连接
2. 应用概述
如今,智能手机拍照功能几乎“好得过头”了。拍照者能够可靠地拍出模糊不清的神秘照片的日子已经过去了。
在本 Codelab 中,您将使用 Blur-O-Matic 应用,它可以模糊照片并将结果保存到文件中。那是 尼斯湖水怪还是玩具潜艇?有了 Blur-O-Matic,没有人会知道!
屏幕上有一些单选按钮,您可以选择所需的模糊程度。点击“开始”按钮即可模糊并保存图像。
目前,应用不会应用任何模糊效果,也不会保存最终图像。
本 Codelab 重点介绍如何将 WorkManager 添加到应用中,创建工作器来清理模糊图像时创建的临时文件,模糊图像以及保存图像的最终副本(点击“查看文件”按钮即可查看)。您还将学习如何监控后台任务的状态并相应地更新应用的 UI。
3. 探索 Blur-O-Matic 启动应用
获取启动代码
要开始,请下载启动代码
或者,您可以克隆代码的 GitHub 代码库
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout starter
您可以在此 GitHub 代码库中浏览 Blur-o-matic 应用的代码。
运行启动代码
要熟悉启动代码,请完成以下步骤
- 在 Android Studio 中打开包含启动代码的项目。
- 在 Android 设备或模拟器上运行应用。
屏幕上有单选按钮,您可以选择图像的模糊量。当您点击“开始”按钮时,应用会模糊并保存图像。
目前,当您点击“开始”按钮时,应用不会应用任何模糊量。
启动代码演练
在此任务中,您将熟悉项目的结构。以下列表提供了项目中重要文件和文件夹的演练。
WorkerUtils
:稍后您将使用此方法显示Notifications
和将位图保存到文件的代码。BlurViewModel
:此视图模型存储应用的状态并与代码库交互。WorkManagerBluromaticRepository
:您在此类中使用 WorkManager 启动后台任务。Constants
:包含您在 Codelab 中使用的某些常量的静态类。BluromaticScreen
:包含 UI 的可组合函数并与BlurViewModel
交互。可组合函数显示图像,并包含用于选择所需模糊级别的单选按钮。
4. 什么是 WorkManager?
WorkManager 是 Android Jetpack 的一部分,也是一个用于需要结合机会性执行和保证执行的后台任务的 架构组件。机会性执行意味着 WorkManager 会尽快完成您的后台任务。保证执行意味着 WorkManager 会处理在各种情况下(即使您离开应用)启动您的任务的逻辑。
WorkManager 是一个非常灵活的库,具有许多额外优势。其中一些优势包括:
- 支持异步一次性任务和周期性任务。
- 支持约束条件,例如网络条件、存储空间和充电状态。
- 复杂工作请求的链接,例如并行运行工作。
- 将一个工作请求的输出用作下一个工作请求的输入。
- 处理 API 级别兼容性,回溯到 API 级别 14(参见注释)。
- 有或没有 Google Play 服务都可以工作。
- 遵循系统健康最佳实践。
- 支持轻松地在应用的 UI 中显示工作请求的状态。
5. 何时使用 WorkManager
如果您需要完成一些任务,WorkManager 库是一个不错的选择。这些任务的运行不依赖于在排队工作后应用继续运行。即使应用关闭或用户返回主屏幕,这些任务也会运行。
一些适合使用 WorkManager 的任务示例:
- 定期查询最新的新闻故事。
- 对图像应用滤镜,然后保存图像。
- 定期将本地数据与网络同步。
WorkManager 是在主线程之外运行任务的一种选择,但它并非用于在主线程之外运行所有类型的任务的万能方法。以前的 Codelab 讨论了 协程 作为另一种选择。
有关何时使用 WorkManager 的更多详细信息,请查看 后台工作指南。
6. 将 WorkManager 添加到您的应用
WorkManager
需要以下 Gradle 依赖项。此依赖项已包含在构建文件中
app/build.gradle.kts
dependencies {
// WorkManager dependency
implementation("androidx.work:work-runtime-ktx:2.8.1")
}
您必须在应用中使用 work-runtime-ktx
的最新 稳定版本。
如果更改了版本,请确保点击“立即同步”以使您的项目与更新的 Gradle 文件同步。
7. WorkManager 基础知识
您需要了解一些 WorkManager 类
Worker
/CoroutineWorker
:Worker 是一个在后台线程上同步执行工作的类。由于我们对异步工作感兴趣,因此我们可以使用 CoroutineWorker,它具有与 Kotlin 协程的互操作性。在此应用中,您从 CoroutineWorker 类继承并覆盖doWork()
方法。在此方法中,您可以放置要在后台执行的实际工作的代码。WorkRequest
:此类表示执行某些工作的请求。WorkRequest
用于定义是否需要一次或定期运行工作器。还可以对WorkRequest
设置 约束条件,这些约束条件要求在工作运行之前必须满足某些条件。一个示例是在启动请求的工作之前设备正在充电。您将CoroutineWorker
作为创建WorkRequest
的一部分传入。WorkManager
:此类实际调度您的WorkRequest
并使其运行。它以一种在系统资源上分散负载的方式调度WorkRequest
,同时遵守您指定的约束条件。
在本例中,您定义一个新的 BlurWorker
类,其中包含模糊图像的代码。当您点击“开始”按钮时,WorkManager 会创建并排队一个 WorkRequest
对象。
8. 创建 BlurWorker
在此步骤中,您将 res/drawable
文件夹中名为 android_cupcake.png
的图像在后台运行一些函数。这些函数会模糊图像。
- 右键点击 Android 项目窗格中的包
com.example.bluromatic.workers
,然后选择“新建 -> Kotlin 类/文件”。 - 将新的 Kotlin 类命名为
BlurWorker
。使用所需的构造函数参数从CoroutineWorker
扩展它。
workers/BlurWorker.kt
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
}
BlurWorker
类继承自 CoroutineWorker
类,而不是更通用的 Worker
类。CoroutineWorker
类对 doWork()
的实现是一个挂起函数,允许它运行 Worker
无法执行的异步代码。如指南 WorkManager 中的多线程处理 中所述,“对于 Kotlin 用户,CoroutineWorker 是推荐的实现方式”。
此时,Android Studio 会在 class BlurWorker
下方绘制一条红色的波浪线,指示出现错误。
如果将光标悬停在 class BlurWorker
文本上,IDE 将显示一个包含有关错误的附加信息的弹出窗口。
错误消息指示您未按要求重写 doWork()
方法。
在 doWork()
方法中,编写代码来模糊显示的纸杯蛋糕图像。
按照以下步骤修复错误并实现 doWork()
方法
- 单击“BlurWorker”文本,将光标置于类代码内。
- 从 Android Studio 菜单中,选择**代码 > 重写方法…**。
- 从**重写成员**弹出窗口中,选择
doWork()
。 - 单击**确定**。
- 在类声明之前,创建一个名为
TAG
的变量,并将其值赋值为BlurWorker
。请注意,此变量与doWork()
方法没有特定关系,但稍后您会在对Log()
的调用中使用它。
workers/BlurWorker.kt
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
...
- 为了更好地查看工作执行的时间,您需要使用
WorkerUtil
的makeStatusNotification()
函数。此函数允许您轻松地在屏幕顶部显示通知横幅。
在 doWork()
方法内,使用 makeStatusNotification()
函数显示状态通知,并通知用户模糊工作已启动并正在模糊图像。
workers/BlurWorker.kt
import com.example.bluromatic.R
...
override suspend fun doWork(): Result {
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
...
- 添加一个
return try...catch
代码块,这是执行实际模糊图像工作的地方。
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
} catch (throwable: Throwable) {
}
...
- 在
try
块中,添加对Result.success()
的调用。 - 在
catch
块中,添加对Result.failure()
的调用。
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
Result.success()
} catch (throwable: Throwable) {
Result.failure()
}
...
- 在
try
块中,创建一个名为picture
的新变量,并使用调用BitmapFactory.decodeResource
()
方法并传入应用程序的资源包和纸杯蛋糕图像的资源 ID 返回的位图填充它。
workers/BlurWorker.kt
...
return try {
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
Result.success()
...
- 通过调用
blurBitmap()
函数并传入picture
变量和blurLevel
参数的值 1(一)来模糊位图。 - 将结果保存在名为
output
的新变量中。
workers/BlurWorker.kt
...
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
val output = blurBitmap(picture, 1)
Result.success()
...
- 创建一个新的变量
outputUri
,并使用对writeBitmapToFile()
函数的调用填充它。 - 在对
writeBitmapToFile()
的调用中,传入应用程序上下文和output
变量作为参数。
workers/BlurWorker.kt
...
val output = blurBitmap(picture, 1)
// Write bitmap to a temp file
val outputUri = writeBitmapToFile(applicationContext, output)
Result.success()
...
- 添加代码以向用户显示包含
outputUri
变量的通知消息。
workers/BlurWorker.kt
...
val outputUri = writeBitmapToFile(applicationContext, output)
makeStatusNotification(
"Output is $outputUri",
applicationContext
)
Result.success()
...
- 在
catch
块中,记录一条错误消息以指示在尝试模糊图像时发生错误。对Log.e()
的调用传递先前定义的TAG
变量、适当的消息和正在抛出的异常。
workers/BlurWorker.kt
...
} catch (throwable: Throwable) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_applying_blur),
throwable
)
Result.failure()
}
...
CoroutineWorker
默认情况下以 Dispatchers.Default
运行,但可以通过调用 withContext()
并传入所需的调度程序来更改。
- 创建一个
withContext()
块。 - 在对
withContext()
的调用中,传递Dispatchers.IO
,以便 lambda 函数在一个特殊的线程池中运行,用于潜在的阻塞 IO 操作。 - 将先前编写的
return try...catch
代码移到此块中。
...
return withContext(Dispatchers.IO) {
return try {
// ...
} catch (throwable: Throwable) {
// ...
}
}
...
Android Studio 显示以下错误,因为您无法从 lambda 函数中调用 return
。
可以通过添加标签来修复此错误,如弹出窗口中所示。
...
//return try {
return@withContext try {
...
由于此 Worker 运行速度非常快,建议在代码中添加延迟以模拟运行速度较慢的工作。
- 在
withContext()
lambda 内,添加对delay()
实用程序函数的调用,并传入DELAY_TIME_MILLIS
常量。此调用严格用于代码实验室,以便在通知消息之间提供延迟。
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay
...
return withContext(Dispatchers.IO) {
// This is an utility function added to emulate slower work.
delay(DELAY_TIME_MILLIS)
val picture = BitmapFactory.decodeResource(
...
9. 更新 WorkManagerBluromaticRepository
存储库处理与 WorkManager 的所有交互。此结构遵循 关注点分离的设计原则,并且是推荐的 Android 架构模式。
- 在
data/WorkManagerBluromaticRepository.kt
文件中,在WorkManagerBluromaticRepository
类内,创建一个名为workManager
的私有变量,并通过调用WorkManager.getInstance(context)
将WorkManager
实例存储在其中。
data/WorkManagerBluromaticRepository.kt
import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
// New code
private val workManager = WorkManager.getInstance(context)
...
在 WorkManager 中创建和排队 WorkRequest
好了,是时候创建一个 WorkRequest
并告诉 WorkManager 运行它了!有两种类型的 WorkRequest
OneTimeWorkRequest
:仅执行一次的WorkRequest
。PeriodicWorkRequest
:按周期重复执行的WorkRequest
。
您只希望在单击**开始**按钮时图像模糊一次。
此工作发生在 applyBlur()
方法中,您在单击**开始**按钮时调用此方法。
以下步骤在 applyBlur()
方法内完成。
- 通过为模糊工作程序创建
OneTimeWorkRequest
并从 WorkManager KTX 调用OneTimeWorkRequestBuilder
扩展函数来填充名为blurBuilder
的新变量。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
- 通过对您的
workManager
对象调用enqueue()
方法来启动工作。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// Start the work
workManager.enqueue(blurBuilder.build())
}
- 运行应用程序,并在单击**开始**按钮时查看通知。
此时,无论您选择哪个选项,图像模糊的程度都相同。在后面的步骤中,模糊量将根据所选选项而变化。
要确认图像是否成功模糊,您可以在 Android Studio 中打开 设备资源管理器
然后导航到data > data > com.example.bluromatic > files > blur_filter_outputs >
10. 输入数据和输出数据
模糊资源目录中的图像资源很好,但是为了让 Blur-O-Matic 真正成为它注定要成为的革命性图像编辑应用程序,您需要允许用户模糊他们在屏幕上看到的图像,然后向他们显示模糊的结果。
为此,我们将显示的纸杯蛋糕图像的 URI 提供为 WorkRequest
的输入,然后使用 WorkRequest
的输出显示最终的模糊图像。
输入和输出通过 Data
对象传入和传出工作程序。Data
对象是键/值对的轻量级容器。它们旨在存储少量数据,这些数据可能会从 WorkRequest
传入和传出工作程序。
在下一步中,您将通过创建输入数据对象将 URI 传递给 BlurWorker
。
创建输入数据对象
- 在
data/WorkManagerBluromaticRepository.kt
文件中,在WorkManagerBluromaticRepository
类内,创建一个名为imageUri
的新私有变量。 - 通过调用上下文方法
getImageUri()
来填充变量。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
private var imageUri: Uri = context.getImageUri() // <- Add this
private val workManager = WorkManager.getInstance(context)
...
应用程序代码包含用于创建输入数据对象的 createInputDataForWorkRequest()
辅助函数。
data/WorkManagerBluromaticRepository.kt
// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
val builder = Data.Builder()
builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
return builder.build()
}
首先,辅助函数创建一个 Data.Builder
对象。然后它将 imageUri
和 blurLevel
作为键/值对放入其中。当它调用 return builder.build()
时,将创建一个 Data 对象并返回。
- 要为 WorkRequest 设置输入数据对象,您可以调用
blurBuilder.setInputData()
方法。您可以通过将createInputDataForWorkRequest()
辅助函数作为参数调用来一步创建和传递数据对象。对于对createInputDataForWorkRequest()
函数的调用,请传入blurLevel
变量和imageUri
变量。
data/WorkManagerBluromaticRepository.kt
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// New code for input data object
blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))
workManager.enqueue(blurBuilder.build())
}
访问输入数据对象
现在让我们更新 BlurWorker
类中的 doWork()
方法,以获取输入数据对象传入的 URI 和模糊级别。如果没有提供 blurLevel
的值,则默认为 1
。
在 doWork()
方法内
- 创建一个名为
resourceUri
的新变量,并通过调用inputData.getString()
并传入创建输入数据对象时使用的常量KEY_IMAGE_URI
来填充变量。
val resourceUri = inputData.getString(KEY_IMAGE_URI)
- 创建一个名为
blurLevel
的新变量。通过调用inputData.getInt()
并传入创建输入数据对象时用作键的常量BLUR_LEVEL
来填充该变量。如果未创建此键值对,则提供默认值1
(一)。
workers/BlurWorker.kt
import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {
// ADD THESE LINES
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)
// ... rest of doWork()
}
有了URI,现在让我们模糊屏幕上纸杯蛋糕的图像。
- 检查
resourceUri
变量是否已填充。如果未填充,则代码应抛出异常。以下代码使用require()
语句,如果第一个参数计算结果为false,则抛出IllegalArgumentException
。
workers/BlurWorker.kt
return@withContext try {
// NEW code
require(!resourceUri.isNullOrBlank()) {
val errorMessage =
applicationContext.resources.getString(R.string.invalid_input_uri)
Log.e(TAG, errorMessage)
errorMessage
}
由于图像源作为URI传入,因此我们需要一个ContentResolver对象来读取URI指向的内容。
- 将
contentResolver
对象添加到applicationContext
值。
workers/BlurWorker.kt
...
require(!resourceUri.isNullOrBlank()) {
// ...
}
val resolver = applicationContext.contentResolver
...
- 因为图像源现在是传入的URI,所以使用
BitmapFactory.decodeStream()
而不是BitmapFactory.decodeResource()
来创建Bitmap对象。
workers/BlurWorker.kt
import android.net.Uri
...
// val picture = BitmapFactory.decodeResource(
// applicationContext.resources,
// R.drawable.android_cupcake
// )
val resolver = applicationContext.contentResolver
val picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
- 在对
blurBitmap()
函数的调用中传入blurLevel
变量。
workers/BlurWorker.kt
//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)
创建输出数据对象
您现在已完成此Worker,可以将输出URI作为输出数据对象返回到Result.success()
中。将输出URI作为输出数据对象可以方便其他worker进一步操作。当您创建一系列worker时,这种方法在下一节中非常有用。
为此,请完成以下步骤
- 在
Result.success()
代码之前,创建一个名为outputData
的新变量。 - 通过调用
workDataOf()
函数并使用常量KEY_IMAGE_URI
作为键和变量outputUri
作为值来填充此变量。workDataOf()
函数从传入的键值对创建一个Data对象。
workers/BlurWorker.kt
import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
- 更新
Result.success()
代码以接受此新的Data对象作为参数。
workers/BlurWorker.kt
//Result.success()
Result.success(outputData)
- 删除显示通知的代码,因为它不再需要,因为输出Data对象现在使用URI。
workers/BlurWorker.kt
// REMOVE the following notification code
//makeStatusNotification(
// "Output is $outputUri",
// applicationContext
//)
运行您的应用
此时,运行应用程序时,可以预期它会编译。您可以通过**设备资源管理器**看到模糊的图像,但目前还不能在屏幕上看到。
请注意,您可能需要**同步**才能查看您的图像
做得很好!您已经使用WorkManager
模糊了输入图像!
11. 将您的工作链接起来
现在,您正在执行单个工作任务——模糊图像。此任务是一个很好的第一步,但应用程序仍然缺少一些核心功能
- 应用程序不会清理临时文件。
- 应用程序实际上并没有将图像保存到永久文件。
- 应用程序始终以相同的程度模糊图片。
您可以使用WorkManager工作链来添加此功能。WorkManager允许您创建按顺序或并行运行的单独WorkerRequest
。
在本节中,您将创建一个如下所示的工作链
方框代表WorkRequest
。
链接的另一个特性是它能够接受输入并产生输出。一个WorkRequest
的输出成为链中下一个WorkRequest
的输入。
您已经有了一个CoroutineWorker
来模糊图像,但您还需要一个CoroutineWorker
来清理临时文件和一个CoroutineWorker
来永久保存图像。
创建CleanupWorker
CleanupWorker
删除临时文件(如果存在)。
- 右键点击 Android 项目窗格中的包
com.example.bluromatic.workers
,然后选择“新建 -> Kotlin 类/文件”。 - 将新的Kotlin类命名为
CleanupWorker
。 - 复制CleanupWorker.kt的代码,如下面的代码示例所示。
由于文件操作超出了本Codelab的范围,您可以复制以下CleanupWorker
代码。
workers/CleanupWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
/**
* Cleans up temporary files generated during blurring process
*/
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
/** Makes a notification when the work starts and slows down the work so that it's easier
* to see each WorkRequest start, even on emulated devices
*/
makeStatusNotification(
applicationContext.resources.getString(R.string.cleaning_up_files),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
return@withContext try {
val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
if (outputDirectory.exists()) {
val entries = outputDirectory.listFiles()
if (entries != null) {
for (entry in entries) {
val name = entry.name
if (name.isNotEmpty() && name.endsWith(".png")) {
val deleted = entry.delete()
Log.i(TAG, "Deleted $name - $deleted")
}
}
}
}
Result.success()
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_cleaning_file),
exception
)
Result.failure()
}
}
}
}
创建SaveImageToFileWorker
SaveImageToFileWorker
类将临时文件保存到永久文件。
SaveImageToFileWorker
接收输入和输出。输入是临时模糊图像URI的String
,使用键KEY_IMAGE_URI
存储。输出是已保存的模糊图像URI的String
,使用键KEY_IMAGE_URI
存储。
- 右键点击 Android 项目窗格中的包
com.example.bluromatic.workers
,然后选择“新建 -> Kotlin 类/文件”。 - 将新的Kotlin类命名为
SaveImageToFileWorker
。 - 复制SaveImageToFileWorker.kt代码,如下面的示例代码所示。
由于文件操作超出了本Codelab的范围,您可以复制以下SaveImageToFileWorker
代码。在提供的代码中,请注意resourceUri
和output
值是如何使用键KEY_IMAGE_URI
检索和存储的。此过程与您之前为输入和输出数据对象编写的代码非常相似。
workers/SaveImageToFileWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
/**
* Saves the image to a permanent file
*/
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
private val title = "Blurred Image"
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z",
Locale.getDefault()
)
override suspend fun doWork(): Result {
// Makes a notification when the work starts and slows down the work so that
// it's easier to see each WorkRequest start, even on emulated devices
makeStatusNotification(
applicationContext.resources.getString(R.string.saving_image),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
val resolver = applicationContext.contentResolver
return@withContext try {
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
val imageUrl = MediaStore.Images.Media.insertImage(
resolver, bitmap, title, dateFormatter.format(Date())
)
if (!imageUrl.isNullOrEmpty()) {
val output = workDataOf(KEY_IMAGE_URI to imageUrl)
Result.success(output)
} else {
Log.e(
TAG,
applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
)
Result.failure()
}
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_saving_image),
exception
)
Result.failure()
}
}
}
}
创建一个工作链
目前,代码只创建并运行单个WorkRequest
。
在此步骤中,您将修改代码以创建和执行一系列WorkRequests,而不仅仅是一个模糊图像请求。
在WorkRequests链中,您的第一个工作请求是清理临时文件。
- 不要调用
OneTimeWorkRequestBuilder
,而是调用workManager.beginWith()
。
调用beginWith()
方法将返回一个WorkContinuation
对象,并创建一系列WorkRequest
的起点,其中包含链中的第一个工作请求。
data/WorkManagerBluromaticRepository.kt
import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
override fun applyBlur(blurLevel: Int) {
// Add WorkRequest to Cleanup temporary images
var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))
// Add WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...
您可以通过调用then()
方法并传入WorkRequest
对象来添加到此工作请求链中。
- 删除对
workManager.enqueue(blurBuilder.build())
的调用,该调用仅排队一个工作请求。 - 通过调用
.then()
方法将下一个工作请求添加到链中。
data/WorkManagerBluromaticRepository.kt
...
//workManager.enqueue(blurBuilder.build())
// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
- 创建一个保存图像的工作请求并将其添加到链中。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.SaveImageToFileWorker
...
continuation = continuation.then(blurBuilder.build())
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.build()
continuation = continuation.then(save)
...
- 要启动工作,请在continuation对象上调用
enqueue()
方法。
data/WorkManagerBluromaticRepository.kt
...
continuation = continuation.then(save)
// Start the work
continuation.enqueue()
...
此代码生成并运行以下WorkRequests链:一个CleanupWorker
WorkRequest
,后跟一个BlurWorker
WorkRequest
,后跟一个SaveImageToFileWorker
WorkRequest
。
- 运行应用程序。
您现在可以单击**开始**,并在不同的worker执行时查看通知。您仍然可以在**设备资源管理器**中看到模糊的图像,在接下来的部分中,您将添加一个额外的按钮,以便用户可以在设备上看到模糊的图像。
在以下屏幕截图中,请注意通知消息显示当前正在运行哪个worker。
请注意,输出文件夹包含多个模糊图像——处于中间模糊阶段的图像以及显示您选择的模糊量的最终图像。
出色的工作!现在,您可以清理临时文件、模糊图像并保存它!
12. 获取解决方案代码
要下载完成的Codelab代码,您可以使用以下命令
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout intermediate
或者,您可以将存储库下载为zip文件,解压缩它,然后在Android Studio中打开它。
如果您想查看此Codelab的解决方案代码,请在GitHub上查看。
13. 结论
恭喜!您已经完成了Blur-O-Matic应用程序,并在此过程中了解了
- 将WorkManager添加到您的项目
- 计划
OneTimeWorkRequest
- 输入和输出参数
- 将工作链接在一起
WorkRequest
s
WorkManager支持的内容远不止我们在本Codelab中介绍的内容,包括重复性工作、测试支持库、并行工作请求和输入合并器。
要了解更多信息,请访问使用WorkManager安排任务文档。