1. 简介
Android 上有很多用于延迟后台任务的选项。本 Codelab 介绍了 WorkManager,这是一个与其他组件兼容、灵活且简单的用于延迟后台任务的库。WorkManager 是 Android 上推荐的任务调度程序,用于执行延迟工作,并保证其执行。
什么是 WorkManager
WorkManager 是 Android Jetpack 的一部分,也是一个用于后台工作的 架构组件,需要结合机会性执行和保证性执行。机会性执行意味着 WorkManager 会尽快完成您的后台工作。保证性执行意味着 WorkManager 将负责在各种情况下启动工作的逻辑,即使您从应用中导航离开。
WorkManager 是一个简单但非常灵活的库,具有许多其他优势。其中包括
- 支持异步的一次性任务和周期性任务
- 支持约束,例如网络条件、存储空间和充电状态
- 复杂工作请求的链接,包括并行运行工作
- 将一个工作请求的输出用作下一个工作请求的输入
- 处理 API 级别兼容性,回溯到 API 级别 14(请参阅注释)
- 无论是否使用 Google Play 服务均可使用
- 遵循系统健康最佳实践
- LiveData 支持,可轻松在 UI 中显示工作请求状态
何时使用 WorkManager
对于即使用户从特定屏幕或应用中导航离开也需要完成的任务,WorkManager 库是一个不错的选择。
一些适合使用 WorkManager 的任务示例
- 上传日志
- 对图像应用滤镜并保存图像
- 定期将本地数据与网络同步
WorkManager 提供了保证性执行,但并非所有任务都需要此功能。因此,它并非所有脱离主线程运行的任务的万能解决方案。有关何时使用 WorkManager 的更多详细信息,请查看 后台处理指南。
您将构建的内容
如今,智能手机拍摄照片的功能几乎太好了。摄影师再也无法轻松拍出神秘事物模糊的照片了。
在本 Codelab 中,您将开发 Blur-O-Matic 应用,该应用可以模糊照片和图像,并将结果保存到文件。那是 尼斯湖水怪还是开发者的一艘玩具潜艇?有了 Blur-O-Matic,就没人知道了。
您将学到什么
- 将 WorkManager 添加到您的项目中
- 安排一个简单的任务
- 输入和输出参数
- 链接工作
- 唯一的工作
- 在 UI 中显示工作状态
- 取消工作
- 工作约束
您需要什么
- 最新版本的 Android Studio 稳定版
- 您还应该熟悉
LiveData
和ViewModel
。如果您不熟悉这些类,请查看 Android 生命周期感知组件 Codelab(特别是关于 ViewModel 和 LiveData 的部分)或 Room with a View Codelab(架构组件简介)。
如果您在任何时候遇到困难...
如果您在任何时候遇到本 Codelab 的问题,或者想要查看代码的最终状态,可以使用以下链接
或者,如果您愿意,可以从 GitHub 克隆已完成的 WorkManager Codelab 代码
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
2. 设置
步骤 1 - 下载代码
点击以下链接下载本 Codelab 的所有代码
或者,如果您愿意,可以从 GitHub 克隆导航 Codelab 代码
$ git clone -b start_java https://github.com/googlecodelabs/android-workmanager
步骤 2 - 运行应用
运行应用。您应该会看到以下屏幕
屏幕上应该有单选按钮,您可以选择希望图像的模糊程度。按下“Go”按钮最终将模糊并保存图像。
目前,应用不会应用任何模糊效果。
起始代码包含
WorkerUtils
:此类包含实际模糊图像的代码,以及一些稍后将用于显示Notifications
、将位图保存到文件以及减慢应用速度的便捷方法。BlurActivity
:显示图像并包含用于选择模糊级别的单选按钮的 Activity。BlurViewModel
:此视图模型存储显示BlurActivity
所需的所有数据。它也将是使用 WorkManager 启动后台工作的类。Constants
:一个包含您将在 Codelab 中使用的某些常量的静态类。res/activity_blur.xml
:BlurActivity
的布局文件。
您将在以下类中进行代码更改:BlurActivity
和 BlurViewModel
。
3. 将 WorkManager 添加到您的应用
WorkManager
需要以下 Gradle 依赖项。这些已包含在构建文件中
app/build.gradle
dependencies {
// WorkManager dependency
implementation "androidx.work:work-runtime:$versions.work"
}
您应该从 此处 获取最新版本的 work-runtime
并将其放入。目前最新版本为
build.gradle
versions.work = "2.7.1"
如果您将版本更新到较新的版本,请确保立即同步以使您的项目与更改后的 Gradle 文件同步。
4. 创建您的第一个 WorkRequest
在此步骤中,您将获取 res/drawable
文件夹中名为 android_cupcake.png
的图像,并在后台对其运行一些函数。这些函数将模糊图像并将其保存到临时文件。
WorkManager 基础知识
您需要了解一些 WorkManager 类
Worker
:这是您放置要在后台执行的实际工作的代码的地方。您将扩展此类并覆盖doWork()
方法。WorkRequest
:这表示执行某些工作的请求。您将传入您的Worker
作为创建WorkRequest
的一部分。在创建WorkRequest
时,您还可以指定诸如Constraints
之类的内容,以指示Worker
应何时运行。WorkManager
:此类实际上会安排您的WorkRequest
并使其运行。它会以一种分散系统资源负载的方式安排WorkRequest
,同时遵守您指定的约束。
在您的情况下,您将定义一个新的 BlurWorker
,其中将包含模糊图像的代码。当点击“Go”按钮时,将创建 WorkRequest
,然后由 WorkManager
入队。
步骤 1 - 创建 BlurWorker
在包 workers
中,创建一个名为 BlurWorker
的新类。
它应该扩展 Worker
。
步骤 2 - 添加构造函数
向 BlurWorker
类添加一个构造函数
public class BlurWorker extends Worker {
public BlurWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
}
步骤 3 - 覆盖并实现 doWork()
您的 Worker
将模糊显示的纸杯蛋糕图像。
为了更好地了解何时执行工作,您将使用 WorkerUtil 的 makeStatusNotification()
。此方法将让您轻松地在屏幕顶部显示通知横幅。
覆盖 doWork()
方法,然后实现以下操作
- 通过调用
getApplicationContext()
获取Context
。您将需要它来执行即将执行的各种位图操作。 - 从测试图像创建
Bitmap
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.android_cupcake);
- 通过调用
WorkerUtils
中的静态blurBitmap
方法获取位图的模糊版本。 - 通过调用
WorkerUtils
中的静态writeBitmapToFile
方法将该位图写入临时文件。确保将返回的 URI 保存到局部变量。 - 通过调用
WorkerUtils
中的静态makeStatusNotification
方法创建显示 URI 的通知。 - 返回
Result.success();
- 将步骤 2-6 中的代码包装在 try/catch 语句中。捕获泛型
Throwable
。 - 在 catch 语句中,发出错误日志语句:
Log.e(TAG, "Error applying blur", throwable);
- 然后在 catch 语句中返回
Result.failure();
此步骤的完整代码如下所示。
BlurWorker.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import com.example.background.R;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class BlurWorker extends Worker {
public BlurWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = BlurWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
try {
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.android_cupcake);
// Blur the bitmap
Bitmap output = WorkerUtils.blurBitmap(picture, applicationContext);
// Write bitmap to a temp file
Uri outputUri = WorkerUtils.writeBitmapToFile(applicationContext, output);
WorkerUtils.makeStatusNotification("Output is "
+ outputUri.toString(), applicationContext);
// If there were no errors, return SUCCESS
return Result.success();
} catch (Throwable throwable) {
// Technically WorkManager will return Result.failure()
// but it's best to be explicit about it.
// Thus if there were errors, we're return FAILURE
Log.e(TAG, "Error applying blur", throwable);
return Result.failure();
}
}
}
步骤 4 - 在 ViewModel 中获取 WorkManager
在您的 ViewModel
中为 WorkManager
实例创建一个变量,并在 ViewModel
的构造函数中对其进行实例化
BlurViewModel.java
private WorkManager mWorkManager;
// BlurViewModel constructor
public BlurViewModel(@NonNull Application application) {
super(application);
mWorkManager = WorkManager.getInstance(application);
//...rest of the constructor
}
步骤 5 - 在 WorkManager 中入队 WorkRequest
好了,是时候创建 WorkRequest 并告诉 WorkManager 运行它了。有两种类型的 WorkRequest
OneTimeWorkRequest:
只会执行一次的WorkRequest
。PeriodicWorkRequest:
会按周期重复的WorkRequest
。
我们只希望在点击“Go”按钮时模糊图像一次。当点击“Go”按钮时会调用 applyBlur
方法,因此请在其中从 BlurWorker
创建一个 OneTimeWorkRequest
。然后,使用您的 WorkManager
实例将您的 WorkRequest
入队。
将以下代码行添加到BlurViewModel
的applyBlur()方法中
BlurViewModel.java
void applyBlur(int blurLevel) {
mWorkManager.enqueue(OneTimeWorkRequest.from(BlurWorker.class));
}
步骤 6 - 运行代码!
运行您的代码。它应该能够编译,并且当您按下Go按钮时,您应该会看到通知。
您也可以在 Android Studio 中打开设备文件浏览器
然后导航到data>data>com.example.background>files>blur_filter_outputs><URI> 并确认鱼确实被模糊了
5. 添加输入和输出
模糊资源目录中的图像资产固然很好,但要使 Blur-O-Matic 真正成为它注定要成为的革命性图像编辑应用程序,您应该允许用户模糊他们在屏幕上看到的图像,然后能够向他们显示模糊的结果。
为此,我们将显示为输入的纸杯蛋糕图像的 URI 提供给我们的WorkRequest
。
步骤 1 - 创建数据输入对象
输入和输出通过Data
对象传入和传出。Data
对象是键值对的轻量级容器。它们旨在存储可能传入和传出WorkRequest
的少量数据。
您将把用户图像的 URI 传入一个 bundle 中。该 URI 存储在一个名为mImageUri
的变量中。
创建一个名为createInputDataForUri
的私有方法。此方法应
- 创建一个
Data.Builder
对象。 - 如果
mImageUri
是一个非空URI
,则使用putString
方法将其添加到Data
对象中。此方法接受一个键和一个值。您可以使用Constants
类中的字符串常量KEY_IMAGE_URI
。 - 在
Data.Builder
对象上调用build()
以创建Data
对象,并将其返回。
以下是已完成的createInputDataForUri
方法
BlurViewModel.java
/**
* Creates the input data bundle which includes the Uri to operate on
* @return Data which contains the Image Uri as a String
*/
private Data createInputDataForUri() {
Data.Builder builder = new Data.Builder();
if (mImageUri != null) {
builder.putString(KEY_IMAGE_URI, mImageUri.toString());
}
return builder.build();
}
步骤 2 - 将 Data 对象传递给 WorkRequest
您需要更改applyBlur
方法,使其
- 创建一个新的
OneTimeWorkRequest.Builder
。 - 调用
setInputData
,传入createInputDataForUri
的结果。 - 构建
OneTimeWorkRequest
。 - 使用
WorkManager
将该请求排队。
以下是已完成的applyBlur
方法
BlurViewModel.java
void applyBlur(int blurLevel) {
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
}
步骤 3 - 更新 BlurWorker 的 doWork() 以获取输入
现在让我们更新BlurWorker
的doWork()
方法以获取我们从Data
对象传入的 URI
BlurWorker.java
public Result doWork() {
Context applicationContext = getApplicationContext();
// ADD THIS LINE
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI);
//... rest of doWork()
}
在您完成后续步骤之前,此变量未使用。
步骤 4 - 模糊给定的 URI
有了 URI,现在让我们模糊屏幕上纸杯蛋糕的图像。
- 删除之前获取图像资源的代码。
Bitmap picture = BitmapFactory.decodeResource(appContext.
resources
, R.drawable.
android_cupcake
)
- 检查从传入的
Data
中获得的resourceUri
是否不为空。 - 将
picture
变量分配为传入的图像,如下所示
Bitmap picture = BitmapFactory.decodeStream(
appContext.
contentResolver
.
`openInputStream(Uri.parse(resourceUri)))`
BlurWorker.java
public Worker.Result doWork() {
Context applicationContext = getApplicationContext();
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI);
try {
// REPLACE THIS CODE:
// Bitmap picture = BitmapFactory.decodeResource(
// applicationContext.getResources(),
// R.drawable.android_cupcake);
// WITH
if (TextUtils.isEmpty(resourceUri)) {
Log.e(TAG, "Invalid input uri");
throw new IllegalArgumentException("Invalid input uri");
}
ContentResolver resolver = applicationContext.getContentResolver();
// Create a bitmap
Bitmap picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
//...rest of doWork
步骤 5 - 输出临时 URI
我们完成了此 Worker,现在可以返回Result.success()
了。我们将提供 OutputURI 作为输出数据,以便其他工作者可以轻松访问此临时图像以进行进一步操作。这在下一章创建工作者链时将很有用。为此
- 创建一个新的
Data
,就像您对输入所做的那样,并将outputUri
存储为String
。使用相同的键KEY_IMAGE_URI
。 - 将其传递给
Worker
的Result.success()
方法。
BlurWorker.java
此行应位于WorkerUtils.makeStatusNotification
行之后,并替换doWork()
中的Result.success()
Data outputData = new Data.Builder()
.putString(KEY_IMAGE_URI, outputUri.toString())
.build();
return Result.success(outputData);
步骤 6 - 运行您的应用程序
此时,您应该运行您的应用程序。它应该能够编译并具有相同的行为,您可以在设备文件浏览器中看到模糊的图像,但尚未在屏幕上显示。
要检查另一个模糊的图像,您可以在 Android Studio 中打开设备文件浏览器并导航到data/data/com.example.background/files/blur_filter_outputs/<URI>,就像您在上一步中所做的那样。
请注意,您可能需要同步才能查看您的图像
干得好!您已使用WorkManager
模糊了输入图像!
6. 链接您的工作
现在,您正在执行一项单一的工作任务:模糊图像。这是很好的一步,但缺少一些核心功能
- 它不会清理临时文件。
- 它实际上不会将图像保存到永久文件中。
- 它总是以相同的量模糊图片。
我们将使用 WorkManager 工作链来添加此功能。
WorkManager 允许您创建按顺序或并行运行的单独WorkerRequest
。在此步骤中,您将创建一个如下所示的工作链
WorkRequest
表示为方框。
链接的另一个非常棒的功能是,一个WorkRequest
的输出成为链中下一个WorkRequest
的输入。在每个WorkRequest
之间传递的输入和输出显示为蓝色文本。
步骤 1 - 创建清理和保存工作者
首先,您将定义所需的所有Worker
类。您已经有一个用于模糊图像的Worker
,但您还需要一个用于清理临时文件的Worker
和一个用于永久保存图像的Worker
。
在worker
包中创建两个新的类,它们扩展Worker
。
第一个应该称为CleanupWorker
,第二个应该称为SaveImageToFileWorker
。
步骤 2 - 添加构造函数
向CleanupWorker
类添加一个构造函数
public class CleanupWorker extends Worker {
public CleanupWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
}
步骤 3 - 覆盖并实现 CleanupWorker 的 doWork()
CleanupWorker
不需要获取任何输入或传递任何输出。如果临时文件存在,它始终会删除它们。由于这不是关于文件操作的 codelab,因此您可以复制下面CleanupWorker
的代码
CleanupWorker.java
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.example.background.Constants;
import java.io.File;
public class CleanupWorker extends Worker {
public CleanupWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = CleanupWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
// 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
WorkerUtils.makeStatusNotification("Cleaning up old temporary files",
applicationContext);
WorkerUtils.sleep();
try {
File outputDirectory = new File(applicationContext.getFilesDir(),
Constants.OUTPUT_PATH);
if (outputDirectory.exists()) {
File[] entries = outputDirectory.listFiles();
if (entries != null && entries.length > 0) {
for (File entry : entries) {
String name = entry.getName();
if (!TextUtils.isEmpty(name) && name.endsWith(".png")) {
boolean deleted = entry.delete();
Log.i(TAG, String.format("Deleted %s - %s",
name, deleted));
}
}
}
}
return Worker.Result.success();
} catch (Exception exception) {
Log.e(TAG, "Error cleaning up", exception);
return Worker.Result.failure();
}
}
}
步骤 4 - 覆盖并实现 SaveImageToFileWorker 的 doWork()
SaveImageToFileWorker
将获取输入和输出。输入是使用键KEY_IMAGE_URI
存储的String
。输出也将是使用键KEY_IMAGE_URI
存储的String
。
由于这仍然不是关于文件操作的 codelab,因此代码如下所示,其中有两个TODO
供您填写输入和输出的相应代码。这与您在上一步中为输入和输出编写的代码非常相似(它使用所有相同的键)。
SaveImageToFileWorker.java
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.example.background.Constants;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class SaveImageToFileWorker extends Worker {
public SaveImageToFileWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = SaveImageToFileWorker.class.getSimpleName();
private static final String TITLE = "Blurred Image";
private static final SimpleDateFormat DATE_FORMATTER =
new SimpleDateFormat("yyyy.MM.dd 'at' HH:mm:ss z", Locale.getDefault());
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
// 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
WorkerUtils.makeStatusNotification("Saving image", applicationContext);
WorkerUtils.sleep();
ContentResolver resolver = applicationContext.getContentResolver();
try {
String resourceUri = getInputData()
.getString(Constants.KEY_IMAGE_URI);
Bitmap bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
String outputUri = MediaStore.Images.Media.insertImage(
resolver, bitmap, TITLE, DATE_FORMATTER.format(new Date()));
if (TextUtils.isEmpty(outputUri)) {
Log.e(TAG, "Writing to MediaStore failed");
return Result.failure();
}
Data outputData = new Data.Builder()
.putString(Constants.KEY_IMAGE_URI, outputUri)
.build();
return Result.success(outputData);
} catch (Exception exception) {
Log.e(TAG, "Unable to save image to Gallery", exception);
return Worker.Result.failure();
}
}
}
步骤 5 - 修改 BlurWorker 通知
现在我们有一系列Worker
负责将图像保存到正确的文件夹中,我们可以修改通知以在工作开始时通知用户并减慢工作速度,以便更容易地看到每个WorkRequest
开始,即使在模拟设备上也是如此。BlurWorker
的最终版本变为
BlurWorker.java
@NonNull
@Override
public Worker.Result doWork() {
Context applicationContext = getApplicationContext();
// 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
WorkerUtils.makeStatusNotification("Blurring image", applicationContext);
WorkerUtils.sleep();
String resourceUri = getInputData().getString(KEY_IMAGE_URI);
try {
if (TextUtils.isEmpty(resourceUri)) {
Log.e(TAG, "Invalid input uri");
throw new IllegalArgumentException("Invalid input uri");
}
ContentResolver resolver = applicationContext.getContentResolver();
// Create a bitmap
Bitmap picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
// Blur the bitmap
Bitmap output = WorkerUtils.blurBitmap(picture, applicationContext);
// Write bitmap to a temp file
Uri outputUri = WorkerUtils.writeBitmapToFile(applicationContext, output);
Data outputData = new Data.Builder()
.putString(KEY_IMAGE_URI, outputUri.toString())
.build();
// If there were no errors, return SUCCESS
return Result.success(outputData);
} catch (Throwable throwable) {
// Technically WorkManager will return Result.failure()
// but it's best to be explicit about it.
// Thus if there were errors, we're return FAILURE
Log.e(TAG, "Error applying blur", throwable);
return Result.failure();
}
}
步骤 6 - 创建 WorkRequest 链
您需要修改BlurViewModel
的applyBlur
方法以执行一系列WorkRequest
,而不仅仅是一个。当前代码如下所示
BlurViewModel.java
void applyBlur(int blurLevel) {
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
}
不要调用WorkManager.enqueue()
,而是调用WorkManager.beginWith()
。这将返回一个WorkContinuation
,它定义了一系列WorkRequest
。您可以通过调用then()
方法来添加到此工作请求链中,例如,如果您有三个WorkRequest
对象,workA
、workB
和workC
,您可以执行以下操作
// Example code. Don't copy to the project
WorkContinuation continuation = mWorkManager.beginWith(workA);
continuation.then(workB) // FYI, then() returns a new WorkContinuation instance
.then(workC)
.enqueue(); // Enqueues the WorkContinuation which is a chain of work
这将生成并运行以下 WorkRequest 链
在applyBlur
中创建一个CleanupWorker
WorkRequest
、一个BlurImage
WorkRequest
和一个SaveImageToFile
WorkRequest
的链。将输入传递到BlurImage
WorkRequest
。
此代码如下所示
BlurViewModel.java
void applyBlur(int blurLevel) {
// Add WorkRequest to Cleanup temporary images
WorkContinuation continuation =
mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// Add WorkRequest to blur the image
OneTimeWorkRequest blurRequest = new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
continuation = continuation.then(blurRequest);
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save =
new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.build();
continuation = continuation.then(save);
// Actually start the work
continuation.enqueue();
}
这应该编译并运行。您现在应该能够点击Go按钮并在不同的工作者执行时看到通知。您仍然可以在设备文件浏览器中看到模糊的图像,并且在即将到来的步骤中,您将添加一个额外的按钮,以便用户可以在设备上看到模糊的图像。
在下面的屏幕截图中,您会注意到通知消息显示了当前正在运行的工作者。
步骤 7 - 重复 BlurWorker
是时候添加以不同量模糊图像的功能了。获取传递到applyBlur
的blurLevel
参数,并将该模糊WorkRequest
操作添加到链中。只有第一个WorkRequest
需要并应该获取 uri 输入。
自己尝试一下,然后与下面的代码进行比较
BlurViewModel.java
void applyBlur(int blurLevel) {
// Add WorkRequest to Cleanup temporary images
WorkContinuation continuation = mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// Add WorkRequests to blur the image the number of times requested
for (int i = 0; i < blurLevel; i++) {
OneTimeWorkRequest.Builder blurBuilder =
new OneTimeWorkRequest.Builder(BlurWorker.class);
// Input the Uri if this is the first blur operation
// After the first blur operation the input will be the output of previous
// blur operations.
if ( i == 0 ) {
blurBuilder.setInputData(createInputDataForUri());
}
continuation = continuation.then(blurBuilder.build());
}
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.build();
continuation = continuation.then(save);
// Actually start the work
continuation.enqueue();
}
打开设备文件浏览器,查看模糊的图像。请注意,输出文件夹包含多个模糊的图像、处于模糊中间阶段的图像以及根据您选择的模糊量显示的最终图像。
极好的“工作”!现在您可以根据需要模糊图像!多么神秘啊!
7. 确保唯一的工作
现在您已经使用了链,是时候解决 WorkManager 的另一个强大功能了 - 唯一工作链。
有时你只想让一个工作链一次运行。例如,也许你有一个工作链将本地数据与服务器同步 - 你可能希望让第一个数据同步完成后再开始新的同步。为此,你将使用beginUniqueWork
而不是 beginWith
;并且你提供一个唯一的 String
名称。这为**整个**工作请求链命名,以便你可以一起引用和查询它们。
通过使用 beginUniqueWork
确保你的模糊文件的工作链是唯一的。传入 IMAGE_MANIPULATION_WORK_NAME
作为键。你还需要传入一个ExistingWorkPolicy
。你的选项是 REPLACE
、KEEP
或 APPEND
。
你将使用 REPLACE
,因为如果用户在当前图像完成之前决定模糊另一张图像,我们希望停止当前图像并开始模糊新图像。
启动唯一工作延续的代码如下
BlurViewModel.java
// REPLACE THIS CODE:
// WorkContinuation continuation =
// mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// WITH
WorkContinuation continuation = mWorkManager
.beginUniqueWork(IMAGE_MANIPULATION_WORK_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.from(CleanupWorker.class));
Blur-O-Matic 现在将始终一次只模糊一张图片。
8. 标记和显示工作状态
本节大量使用LiveData,因此要完全理解正在发生的事情,你应该熟悉 LiveData。LiveData 是一个可观察的、生命周期感知的数据持有者。
如果你第一次使用 LiveData 或可观察对象,可以查看文档或Android 生命周期感知组件 Codelab。
接下来你要做的重大更改是实际更改工作执行时应用程序中显示的内容。
你可以通过获取一个保存WorkInfo
对象的 LiveData
来获取任何 WorkRequest
的状态。WorkInfo
是一个包含有关 WorkRequest
当前状态的详细信息的对象,包括
下表显示了三种获取 LiveData<WorkInfo>
或 LiveData<List<WorkInfo>>
对象的方法以及每种方法的作用。
类型 | WorkManager 方法 | 描述 |
使用**id**获取工作 |
| 每个 |
使用**唯一链名称**获取工作 |
| 正如你所见, |
使用**标签**获取工作 |
| 最后,你可以选择使用字符串标记任何 WorkRequest。你可以使用相同的标签标记多个 |
你将标记 SaveImageToFileWorker
WorkRequest
,以便你可以使用 getWorkInfosByTagLiveData
获取它。你将使用标签标记你的工作而不是使用 WorkManager ID,因为如果你的用户模糊多张图像,所有保存图像的 WorkRequest
将具有相同的标签,但**不**具有相同的 ID。此外,你可以选择标签。
你不会使用 getWorkInfosForUniqueWorkLiveData
,因为这将返回所有模糊 WorkRequest
和清理 WorkRequest
的 WorkInfo
;找到保存图像 WorkRequest
需要额外的逻辑。
步骤 1 - 标记你的工作
在 applyBlur
中,在创建 SaveImageToFileWorker
时,使用 String
常量 TAG_OUTPUT
标记你的工作
BlurViewModel.java
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.addTag(TAG_OUTPUT) // This adds the tag
.build();
步骤 2 - 获取 WorkInfo
现在你已经标记了工作,你可以获取 WorkInfo
- 声明一个名为
mSavedWorkInfo
的新变量,它是一个LiveData<List<WorkInfo>>
- 在
BlurViewModel
构造函数中,使用WorkManager.getWorkInfosByTagLiveData
获取WorkInfo
- 为
mSavedWorkInfo
添加一个 getter
你需要的代码如下
BlurViewModel.java
// New instance variable for the WorkInfo class
private LiveData<List<WorkInfo>> mSavedWorkInfo;
// Placed this code in the BlurViewModel constructor
mSavedWorkInfo = mWorkManager.getWorkInfosByTagLiveData(TAG_OUTPUT);
// Add a getter method for mSavedWorkInfo
LiveData<List<WorkInfo>> getOutputWorkInfo() { return mSavedWorkInfo; }
步骤 3 - 显示 WorkInfo
现在你有了 WorkInfo
的 LiveData
,你可以在 BlurActivity
中观察它。在观察者中
- 检查
WorkInfo
列表是否不为空,以及其中是否包含任何WorkInfo
对象 - 如果没有,则表示尚未点击**Go**按钮,因此返回。 - 获取列表中的第一个
WorkInfo
;将只有一个用TAG_OUTPUT
标记的WorkInfo
,因为我们使工作链唯一。 - 使用
workInfo.getState().isFinished();
检查工作状态是否已完成。 - 如果未完成,则调用
showWorkInProgress()
,它将隐藏并显示相应的视图。 - 如果已完成,则调用
showWorkFinished()
,它将隐藏并显示相应的视图。
代码如下
BlurActivity.java
// Show work status, added in onCreate()
mViewModel.getOutputWorkInfo().observe(this, listOfWorkInfos -> {
// If there are no matching work info, do nothing
if (listOfWorkInfos == null || listOfWorkInfos.isEmpty()) {
return;
}
// We only care about the first output status.
// Every continuation has only one worker tagged TAG_OUTPUT
WorkInfo workInfo = listOfWorkInfos.get(0);
boolean finished = workInfo.getState().isFinished();
if (!finished) {
showWorkInProgress();
} else {
showWorkFinished();
}
});
步骤 4 - 运行你的应用程序
运行你的应用程序 - 它应该可以编译并运行,现在在工作时显示进度条,以及取消按钮
9. 显示最终输出
每个 WorkInfo
还具有一个getOutputData
方法,允许你获取包含最终保存图像的输出 Data
对象。让我们显示一个按钮,在准备好显示模糊图像时显示“查看文件”。
步骤 1 - 创建 mOutputUri
在 BlurViewModel
中为最终 URI 创建一个变量,并为其提供 getter 和 setter。要将 String
转换为 Uri
,可以使用 uriOrNull
方法。
你可以使用以下代码
BlurViewModel.java
// New instance variable for the WorkInfo
private Uri mOutputUri;
// Add a getter and setter for mOutputUri
void setOutputUri(String outputImageUri) {
mOutputUri = uriOrNull(outputImageUri);
}
Uri getOutputUri() { return mOutputUri; }
步骤 2 - 创建“查看文件”按钮
在 activity_blur.xml
布局中已经有一个隐藏的按钮。它位于 BlurActivity
中,可以通过其视图绑定作为 seeFileButton
访问。
设置该按钮的点击监听器。它应该获取 URI,然后打开一个活动来查看该 URI。你可以使用以下代码
BlurActivity.java
// Inside onCreate()
binding.seeFileButton.setOnClickListener(view -> {
Uri currentUri = mViewModel.getOutputUri();
if (currentUri != null) {
Intent actionView = new Intent(Intent.ACTION_VIEW, currentUri);
if (actionView.resolveActivity(getPackageManager()) != null) {
startActivity(actionView);
}
}
});
步骤 3 - 设置 URI 并显示按钮
你需要对 WorkInfo
观察者进行一些最后的调整才能使其工作(没有双关语的意思)
- 如果
WorkInfo
已完成,则使用workInfo.getOutputData().
获取输出数据。 - 然后获取输出 URI,请记住它使用
Constants.KEY_IMAGE_URI
键存储。 - 然后,如果 URI 不为空,则表示已正确保存;显示
seeFileButton
并使用 uri 调用视图模型上的setOutputUri
。
BlurActivity.java
// Replace the observer code we added in previous steps with this one.
// Show work info, goes inside onCreate()
mViewModel.getOutputWorkInfo().observe(this, listOfWorkInfo -> {
// If there are no matching work info, do nothing
if (listOfWorkInfo == null || listOfWorkInfo.isEmpty()) {
return;
}
// We only care about the first output status.
// Every continuation has only one worker tagged TAG_OUTPUT
WorkInfo workInfo = listOfWorkInfo.get(0);
boolean finished = workInfo.getState().isFinished();
if (!finished) {
showWorkInProgress();
} else {
showWorkFinished();
Data outputData = workInfo.getOutputData();
String outputImageUri = outputData.getString(Constants.KEY_IMAGE_URI);
// If there is an output file show "See File" button
if (!TextUtils.isEmpty(outputImageUri)) {
mViewModel.setOutputUri(outputImageUri);
binding.seeFileButton.setVisibility(View.VISIBLE);
}
}
});
步骤 4 - 运行你的代码
运行你的代码。你应该会看到新的可点击的**查看文件**按钮,它会带你到输出的文件
10. 取消工作
你添加了这个**取消工作**按钮,所以让我们添加代码使其执行某些操作。使用 WorkManager,你可以使用 id、标签和唯一链名称取消工作。
在本例中,你希望按唯一链名称取消工作,因为你希望取消链中的所有工作,而不仅仅是特定步骤。
步骤 1 - 按名称取消工作
在视图模型中,编写取消工作的方法
BlurViewModel.java
/**
* Cancel work using the work's unique name
*/
void cancelWork() {
mWorkManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME);
}
步骤 2 - 调用取消方法
然后,将按钮 cancelButton
连接到调用 cancelWork
BlurActivity.java
// In onCreate()
// Hookup the Cancel button
binding.cancelButton.setOnClickListener(view -> mViewModel.cancelWork());
步骤 3 - 运行和取消你的工作
运行你的应用程序。它应该可以正常编译。开始模糊图片,然后点击取消按钮。整个链都被取消了!
请注意,现在工作被取消后只有 GO 按钮,因为 WorkState 不再处于 FINISHED 状态。
11. 工作约束
最后但并非最不重要的一点是,WorkManager
支持Constraints
。对于 Blur-O-Matic,你将使用设备在保存时必须正在充电的约束。
步骤 1 - 创建和添加充电约束
要创建 Constraints
对象,可以使用Constraints.Builder
。然后设置你想要的约束并将其添加到 WorkRequest
中,如下所示
BlurViewModel.java
// In the applyBlur method
// Create charging constraint
Constraints constraints = new Constraints.Builder()
.setRequiresCharging(true)
.build();
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.setConstraints(constraints) // This adds the Constraints
.addTag(TAG_OUTPUT)
.build();
continuation = continuation.then(save);
步骤 2 - 使用模拟器或设备测试
现在你可以运行 Blur-O-Matic。如果你在设备上,可以拔掉或插入你的设备。在模拟器上,可以在**扩展控件窗口**中更改充电状态
当设备未充电时,它应该一直处于加载状态,直到你将其插入为止。
12. 祝贺
恭喜!您已经完成了 Blur-O-Matic 应用,并在过程中学习了以下内容:
- 将 WorkManager 添加到您的项目中
- 计划
OneOffWorkRequest
- 输入和输出参数
- 将工作链接在一起
WorkRequest
s - 命名唯一的
WorkRequest
链 - 标记
WorkRequest
s - 在 UI 中显示
WorkInfo
- 取消
WorkRequest
s - 向
WorkRequest
添加约束
出色的“工作”!要查看代码的最终状态和所有更改,请查看
或者,如果您愿意,可以从 GitHub 克隆 WorkManager 的代码实验室
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
WorkManager 支持的功能远不止我们在本代码实验室中介绍的,包括重复性工作、测试支持库、并行工作请求和输入合并。要了解更多信息,请访问 WorkManager 文档。