Android 有多个选项用于处理可延迟的后台工作。此 Codelab 中介绍的 WorkManager 是一种具有兼容性且简单灵活的库,用于处理可延迟的后台工作。WorkManager 是 Android 平台上推荐的任务调度程序,用于处理可延迟的工作,同时可保证其得到执行。
什么是 WorkManager
WorkManager 属于 Android Jetpack 的一部分,是一种架构组件,用于处理既需要机会性执行,又需要有保证的执行的后台工作。机会性执行意味着 WorkManager 会尽快执行您的后台工作。有保证的执行意味着 WorkManager 会负责通过逻辑保障在各种情况下启动您的工作,即使用户离开您的应用也无妨。
WorkManager 是一个简单又极其灵活的库,具有许多其他优势。这其中包括:
- 支持异步一次性任务和定期任务
- 支持网络条件、存储空间和充电状态等约束条件
- 链接复杂的工作请求,包括并行运行工作
- 将来自一个工作请求的输出用作下一个工作请求的输入
- 处理到 API 级别 14 的 API 级别向后兼容性(请参阅备注)
- 使用或不使用 Google Play 服务
- 遵循系统运行状况最佳做法
- 提供 LiveData 支持,以在界面中轻松显示工作请求状态
何时使用 WorkManager
有些任务,即便用户离开特定屏幕或您的应用,也需要完成。对于这些任务,WorkManager 库是不错的选择。
以下是一些适合使用 WorkManager 的任务的典型示例:
- 上传日志
- 对图片应用滤镜并保存图片
- 定期将本地数据与网络同步
WorkManager 提供有保证的执行,然而并非所有任务都需要这种保证。因此,它并非运行所有非主线程任务的万全之选。如需详细了解何时使用 WorkManager,请参阅后台处理指南。
构建内容
现在,智能手机的拍照功能基本都很强大。摄影师可以给神秘的事物拍一张模糊的照片,这种时代已经一去不复返了。
在本 Codelab 中,您将使用 Blur-O-Matic,该应用可对照片和图片进行模糊处理,并将处理后的照片和图片保存到文件中。那是尼斯湖水怪还是 Evelopera 玩具潜水艇?有了 Blur-O-Matic,没有人能看得出来。
美国农业部农业研究局 Peggy Greb 拍摄的杂交条纹鲈照片 |
学习内容
- 将 WorkManager 添加到您的项目中
- 调度简单的任务
- 输入和输出参数
- 链接工作
- 唯一工作
- 在界面中显示工作状态
- 取消工作
- 工作约束
所需条件
- 最新的稳定版 Android Studio
- 您还应熟悉如何使用
LiveData
和ViewModel
。如果您没有使用过这两个类,请参阅“Android 生命周期感知型组件”Codelab(专门针对 ViewModel 和 LiveData)或“带 View 的 Room”Codelab(架构组件简介)。
如果遇到困难...
如果您在本 Codelab 中遇到困难,或者想查看代码的最终状态,可以查阅以下链接:
如果愿意,您也可以从 GitHub 克隆已完成的 WorkManager 的 Codelab:
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
第 1 步 - 下载代码
点击下面的链接可下载此 Codelab 的所有代码:
如果愿意,您也可以从 GitHub 克隆导航 Codelab:
$ git clone -b start_java https://github.com/googlecodelabs/android-workmanager
第 2 步 - 获取图片
如果您使用的设备已有下载好或拍摄好的照片,那么一切就绪。
如果您使用的是全新设备(例如最近创建的模拟器),则需要用设备拍摄照片或者从网上下载图片。不妨选一些神秘点的!
第 3 步 - 运行应用
运行应用。您应该会看到以下屏幕(请确保在系统首次提示时授予访问照片的权限;如果图片被停用,则重新打开应用):
您可以选择一张图片并进入下一屏幕,然后通过该屏幕上的单选按钮选择要对图片模糊处理的程度。按 Go 按钮即可对图片进行模糊处理并保存。
截至目前,此应用不会应用任何模糊处理。
初始代码包含以下内容:
WorkerUtils
**:** 这个类包含实际进行模糊处理所需的代码,并包含之后您会用于显示Notifications
以及减慢应用运行速度的一些便捷方法。BlurActivity
***:** 此 activity 用于显示图片以及添加用于选择模糊程度的单选按钮。BlurViewModel
***:** 此视图模型用于存储显示BlurActivity
所需的所有数据,也将是您使用 WorkManager 启动后台工作的类。Constants
**:** 一个静态类,其中包含您在学习本 Codelab 期间会用到的一些常量。SelectImageActivity
**:** 第一个 activity,供您选择图片。res/activity_blur.xml
和res/activity_select.xml
:各个 activity 的布局文件。
***** 您将仅在这些文件中编写代码。
WorkManager
需要使用以下 Gradle 依赖项,这些依赖项已包含在 build 文件中:
app/build.gradle
dependencies {
// Other dependencies
implementation "androidx.work:work-runtime:$versions.work"
}
您应该在此处获取最新版 work-runtime
,并部署正确的版本。目前,最新版本为:
build.gradle
versions.work = "2.3.3"
如果您将版本更新为较新的版本,请务必立即同步,将您的项目与已更改的 gradle 文件同步。
在此步骤中,您将在 res/drawable
文件夹中提取一张名为 test.jpg
的图片,并在后台对这张图片运行一些函数。这些函数会对图片进行模糊处理,然后将图片保存到临时文件中。
WorkManager 基础知识
您需要了解以下几个 WorkManager 类:
Worker
:此位置用于放置您希望在后台执行的实际工作的代码。您需要扩展此类并替换doWork()
方法。WorkRequest
:此类表示请求执行某些工作。您将在创建WorkRequest
的过程中传入Worker
。在创建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
会对 res/test.jpg
图片进行模糊处理。
请替换 doWork()
方法,然后执行以下操作:
- 通过调用
getApplicationContext()
获取Context
。您接下来要执行的各种位图处理需要用到此参数。 - 使用测试图片创建
Bitmap
:
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.test);
- 通过调用
WorkerUtils
的静态blurBitmap
方法,获取位图的模糊处理版本。 - 调用
WorkerUtils
的静态writeBitmapToFile
方法,将该位图写入临时文件。请务必将返回的 URI 保存到局部变量。 - 调用
WorkerUtils
的静态makeStatusNotification
方法,以创建显示 URI 的通知。 - 返回
Result.success();
- 在 try/catch 语句中封装第 2-6 步的代码。捕获通用的
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.test);
// 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> 并确认鱼图片确实已进行模糊处理:
对测试图片进行模糊处理固然不错,但如果想让 O-M-Matic 真正成为一款革命性的图片编辑应用,您需要让用户模糊处理他们自己的图片。
为实现此目标,我们将提供用户所选图片的 URI 作为 WorkRequest
的输入。
第 1 步 - 创建数据输入对象
输入和输出通过 Data
对象传入和传出。Data
对象是轻量化的键值对容器。它们用于存储少量可从 WorkRequest
传入和传出的数据。
您需要将用户图片的 URI 传入捆绑包中。该 URI 存储在名为 mImageUri
的变量中。
请创建一个名为 createInputDataForUri
的私有方法。该方法应执行以下操作:
- 创建一个
Data.Builder
对象。 - 如果
mImageUri
是非 nullURI
,则使用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 步 - 将数据对象传递到 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,您可以对用户选择的图片进行模糊处理:
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.test);
// 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
我们已结束此工作器的使用,现在可以返回 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>,就像上一步的操作一样。
请注意,您可能需要点击 Synchronize 才能查看图片:
太棒了!您已使用 WorkManager
对输入图像进行模糊处理!
现在,您将执行一项工作任务:对图片进行模糊处理。这是非常不错的第一步,但缺少一些核心功能:
- 此操作不会清理临时文件。
- 实际上,它不会将图片保存到永久性文件中,
- 而是始终对图片进行相同程度的模糊处理。
我们将使用 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 步 - 替换和实现 doWork() 以用于 CleanupWorker
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 步 - 替换和实现 doWork() 以用于 SaveImageToFileWorker
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.beginWith()
,而不是调用 WorkManager.enqueue()
。此调用会返回 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();
}
此代码应该编译和运行。您现在应该能看到,您选择用于进行模糊处理的图片保存在 Pictures 文件夹中:
第 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();
}
您的“工作”非常不错!现在,您可以对图片进行模糊处理,模糊程度多少完全由您掌控。处理后的图片非常有神秘感。
现在,您已学会使用链,接下来应该掌握的是 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 一次只会对一张图片进行模糊处理。
本部分大量使用了 LiveData,因此,如果要充分了解您自己的情况,您应该熟悉如何使用 LiveData。LiveData 是一种具有生命周期感知能力的数据容器。
如果这是您首次使用 LiveData 或 Observable,您可以查看文档或 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
列表是否不为 null 并且其中是否包含任何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 步 - 运行您的应用
运行您的应用 - 它应该编译并运行,且现在可以在工作时显示进度条以及取消按钮:
每个 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,然后打开一个 activity 以查看该 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 步 - 运行您的代码
运行您的代码。您应该会看到新的可点击的查看文件按钮,该按钮会将您转到输出的文件:
您已添加此取消工作按钮,所以我们要添加一些代码来执行操作。借助 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 步 - 运行和取消工作
运行您的应用。它应该可以正常编译。先对图片进行模糊处理,然后点击“取消”按钮。整个链都会被取消!
最后,很重要的一点是,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 了。如果您使用的是一台设备,则可以移除或插入您的设备。在模拟器上,您可以在“Extended controls”窗口中更改充电状态:
如果设备没有充电,则在插入设备之前,它会一直停留在加载状态。
恭喜!您已学完 Blur-O-Matic 应用的相关知识,且已了解如何执行以下操作:
- 将 WorkManager 添加到您的项目中
- 调度
OneOffWorkRequest
- 输入和输出参数
- 将工作的
WorkRequest
链接到一起 - 命名唯一
WorkRequest
链 - 标记
WorkRequest
- 在界面中显示
WorkInfo
- 取消
WorkRequest
- 为
WorkRequest
添加约束条件
您的“工作”非常出色!如需查看代码的结束状态和所有更改,请执行以下操作:
如果愿意,您也可以从 GitHub 克隆已完成的 WorkManager 的 Codelab:
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
WorkManager 具有许多功能,远非本 Codelab 所能涵盖的,包括重复性工作、测试支持库、并行工作请求以及输入合并。如需了解更多功能,请参阅 WorkManager 文档。