背景使用WorkManager
1。介绍
Android上有许多选项可用于延迟后台工作。此代码库涵盖了WorkManager,这是一个兼容,灵活且简单的库,用于可延迟的后台工作。WorkManager目前处于alpha状态 - 稳定后,它将成为Android上推荐的任务调度程序。
什么是WorkManager
WorkManager是Android Jetpack的一部分,是后台工作的架构组件,需要机会主义和有保证的执行相结合。机会执行意味着WorkManager将尽快完成后台工作。保证执行意味着WorkManager将负责在各种情况下开始工作的逻辑,即使您离开应用程序也是如此。
WorkManager是一个简单但非常灵活的库,具有许多额外的好处。这些包括:
- 支持异步一次性和定期任务
- 支持网络条件,存储空间和计费状态等约束
- 链接复杂的工作请求,包括并行运行工作
- 一个工作请求的输出用作下一个的输入
- 将API级别兼容性处理回API级别14(请参阅注释)
- 使用或不使用Google Play服务
- 遵循系统健康最佳实践
- LiveData支持,可在UI中轻松显示工作请求状态
注意:
WorkManager中坐了几个API,例如顶部JobScheduler
,FirebaseJobDispatcher
和AlarmManager
。WorkManager根据用户的设备API级别以及设备是否可以访问Google Play服务等条件,选择要使用的API。要了解更多信息,请查看WorkManager文档。
何时使用WorkManager
即使用户离开特定屏幕或您的应用程序,WorkManager库也是完成有用任务的不错选择。
一些充分利用WorkManager的任务示例:
- 上传日志
- 将滤镜应用于图像并保存图像
- 定期将本地数据与网络同步
WorkManager提供有保证的执行,并非所有任务都需要。因此,从主线程运行每个任务并不是一件容易的事。有关何时使用WorkManager的更多详细信息,请查看后台处理指南。
你将建立什么
如今,智能手机几乎擅长拍照。摄影师可以拍摄一些神秘的可疑模糊照片的日子已经一去不复返了。
在这个代码框中,你将使用Blur-O-Matic,一个模糊照片和图像并将结果保存到文件的应用程序。那是尼斯湖水怪还是玩具潜艇?有了Blur-O-Matic,没有人会知道。
你将学到什么
- 将WorkManager添加到项目中
- 安排一个简单的任务
- 输入和输出参数
- 链接工作
- 独特的工作
- 在UI中显示工作状态
- 取消工作
- 工作限制
你需要什么
- Android Studio
- 你也应该熟悉
LiveData
和ViewModel
。如果您是这些类的新手,请查看Android生命周期感知组件Codelab(专门用于ViewModel和LiveData)或带有View Codelab的Room(架构组件简介)。
2。设置好
第1步 - 下载代码
单击以下链接下载此codelab的所有代码:
或者如果您愿意,可以从GitHub克隆导航codelab:
$ git clone -b codelab_start https://github.com/googlecodelabs/android-workmanager
$ git clone -b codelab_start https://github.com/googlecodelabs/android-workmanag
第2步 - 获取图像
如果您使用的设备已经下载或在设备上拍照,那么您已经完成了设置。
如果您使用的是全新设备(如最近创建的模拟器),则需要使用设备从网络上拍照或下载图像。选择一些神秘的东西!
第3步 - 运行应用程序
您可以选择一个图像并进入下一个屏幕,该屏幕上有单选按钮,您可以在其中选择您希望图像模糊的程度。按“执行”按钮将模糊并保存图像。
截至目前,该应用程序不适用任何模糊。
起始代码包含:
WorkerUtils
:此类包含实际模糊的代码,以及一些便于稍后用于显示Notifications
和减慢应用程序的便捷方法。BlurActivity
***:显示图像的活动,包括用于选择模糊等级的单选按钮。BlurViewModel
***:此视图模型存储显示所需的所有数据BlurActivity
。它也是使用WorkManager启动后台工作的类。Constants
:一个静态类,包含一些在codelab中使用的常量。SelectImageActivity
:允许您选择图像的第一个活动。res/activity_blur.xml
和res/activity_select.xml
:每个活动的布局文件。
***这些是您编写代码的唯一文件。
3。将WorkManager添加到您的应用程序
WorkManager
需要下面的gradle依赖。继续,现在添加它:
应用程序/的build.gradle
dependencies {
// Other dependencies
implementation "android.arch.work:work-runtime:CURRENT_VERSION"
}
你应该work-runtime
从这里获得最新版本。
确保立即同步以将项目与更改的gradle文件同步。
4。进行第一次WorkRequest
在此步骤中,您将在所res/drawable
调用的文件夹中拍摄图像,test.jpg
并在后台运行一些函数。这些功能会使图像模糊并将其保存到临时文件中。
WorkManager基础知识
您需要了解一些WorkManager类:
Worker
:这是您在后台执行要执行的实际工作的代码的位置。您将扩展此类并覆盖该doWork()
方法。WorkRequest
:这代表了做一些工作的请求。Worker
作为创建你的一部分,你会传入你的WorkRequest
。使得当WorkRequest
你还可以指定喜欢的东西Constraints
时,就Worker
应该运行。WorkManager
:这个课程实际上安排你的WorkRequest
并让它运行。它WorkRequest
以一种分散系统资源负载的方式调度s,同时遵守您指定的约束。
在您的情况下,您将定义一个新的BlurWorker
,其中包含模糊图像的代码。单击“ Go”按钮时,将WorkRequest
创建a,然后将其排入WorkManager
。
第1步 - 制作BlurWorker
在包中workers
,创建一个名为的新类BlurWorker
。
它应该延伸Worker
。
第2步 - 覆盖并实现doWork()
你的Worker
意志会模糊res/test.jpg
图像。
重写该doWork()
方法,然后执行以下操作:
- 获得
Context
通过调用getApplicationContext()
。您将需要这个用于您将要执行的各种位图操作。 Bitmap
从测试图像创建一个:
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.test);
- 通过调用静态
blurBitmap
方法获取位图的模糊版本WorkUtils
。 - 通过调用静态
writeBitmapToFile
方法将该位图写入临时文件WorkUtils
。确保将返回的URI保存到本地变量。 - 通过调用静态
makeStatusNotification
方法创建显示URI的通知WorkUtils
。 - 返回
WorkerResult.SUCCESS
。 - 在try / catch语句中包含步骤2-6中的代码。抓住一个通用的
Throwable
。 - 在catch语句中,发出错误的Log语句:
Log.e(TAG, "Error applying blur", throwable);
- 在catch语句中然后返回
WorkerResult.FAILURE;
完成此步骤的代码如下。
BlurWorker.java
public class BlurWorker extends Worker {
private static final String TAG = BlurWorker.class.getSimpleName();
@NonNull
@Override
public WorkerResult 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 WorkerResult.SUCCESS;
} catch (Throwable throwable) {
// Technically WorkManager will return WorkerResult.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 WorkerResult.FAILURE;
}
第3步 - 在ViewModel中获取WorkManager
为WorkManager
您的实例创建一个变量,ViewModel
并在其ViewModel
构造函数中实例化它:
BlurViewModel.java
private WorkManager mWorkManager;
// BlurViewModel constructor
public BlurViewModel() {
mWorkManager = WorkManager.getInstance();
//...rest of the constructor
}
第4步 - 在WorkManager中将WorkRequest排入队列
好的,是时候做一个WorkRequest并告诉WorkManager运行它。有两种类型的WorkRequest
s:
OneTimeWorkRequest:
WorkRequest
只会执行一次的A.PeriodicWorkRequest:
AWorkRequest
将在循环中重复。
我们只希望在单击Go按钮时图像模糊一次。单击Go按钮applyBlur
时会调用该方法,因此从那里创建一个。然后,使用您的实例将您的实例排入队列OneTimeWorkRequest
BlurWorker
WorkManager
WorkRequest.
BlurViewModel.java
void applyBlur(int blurLevel) {
mWorkManager.enqueue(OneTimeWorkRequest.from(BlurWorker.class));
}
第5步 - 运行你的代码!
运行你的代码。它应该编译,当你按下Go按钮时你应该看到Notification 。
您可以选择在Android Studio中打开设备文件资源管理器:
然后导航到data> data> com.example.background> files> blur_filter_outputs> <URI>并确认鱼实际上是模糊的:
5。添加输入和输出
模糊测试图像一切都很好,但是对于Blur-O-Matic真正成为革命性的图像编辑应用程序而言,它需要让用户模糊自己的图像。
为此,我们将提供用户所选图像的URI作为我们的输入WorkRequest
。
第1步 - 创建数据输入对象
输入和输出通过Data
对象传入和传出。Data
对象是键/值对的轻量级容器。他们是为了存储小数据量,可能通过进入和离开WorkRequest
秒。
您将要将用户图像的URI传递到一个包中。该URI存储在一个名为的变量中mImageUri
。
创建一个名为的私有方法createInputDataForUri
。这种方法应该:
- 创建一个
Data.Builder
对象。 - 如果
mImageUri
是非nullURI
,则Data
使用该putString
方法将其添加到对象。此方法采用键和值。您可以使用字符串常量KEY_IMAGE_URI
从Constants
类。 - 调用
build()
上Data.Builder
的对象,使您的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 WorkerResult doWork() {
Context applicationContext = getApplicationContext();
// ADD THIS LINE
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI, null);
//... rest of doWork()
}
第4步 - 模糊给定的URI
使用URI,您可以模糊用户选择的图像:
BlurWorker.java
public WorkerResult doWork() {
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI, null);
Context applicationContext = getApplicationContext();
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
你还没有使用它,但是让我们继续为我们模糊照片的临时URI 提供输出数据。去做这个:
Data
像输入一样创建一个新的,并存储outputUri
为String
。使用相同的密钥KEY_IMAGE_URI
。- 通过这个来
Worker
的setOutputData
方法。
BlurWorker.java
setOutputData(new Data.Builder().putString(
Constants.KEY_IMAGE_URI, outputUri.toString()).build());
第5步 - 运行您的应用
此时你应该运行你的应用程序。它应该编译并具有相同的行为。
(可选)您可以在Android Studio中打开设备文件资源管理器,然后导航到 数据> data> com.example.background> files> blur_filter_outputs> <URI>,就像在上一步中一样。
请注意,您可能需要同步才能查看图片:
做得好!您使用了模糊输入图像WorkManager
!
6。连接你的工作
现在你正在完成一项工作任务:模糊图像。这是一个很好的第一步,但缺少一些核心功能:
- 它不会清理临时文件。
- 它实际上并不将图像保存为永久文件。
- 它总是模糊图片相同的数量。
我们将使用WorkManager工作链来添加此功能。
WorkManager允许您创建WorkerRequest
按顺序或并行运行的单独s。在此步骤中,您将创建一个如下所示的工作链:
所述WorkRequest
s的表示为方框。
链接的另一个非常巧妙的特征是一个输出WorkRequest
成为WorkRequest
链中下一个的输入。在每个之间传递的输入和输出WorkRequest
显示为蓝色文本。
第1步 - 创建清理并保存工人
首先,您将定义所需的所有Worker
类。您已经有一个Worker
模糊图像,但您还需要一个Worker
清理临时文件和Worker
永久保存图像的文件。
在worker
包中创建两个扩展的新类Worker
。
第一个应该被调用CleanupWorker
,第二个应该被调用SaveImageToFileWorker
。
第2步 - 覆盖并实现CleanupWorker的doWork()
CleanupWorker
不需要任何输入或传递任何输出。它总是删除临时文件(如果存在)。因为这不是关于文件操作的代码库,所以您可以复制以下代码CleanupWorker
:
CleanupWorker.java
public class CleanupWorker extends Worker {
private static final String TAG = CleanupWorker.class.getSimpleName();
@NonNull
@Override
public WorkerResult doWork() {
Context applicationContext = getApplicationContext();
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 WorkerResult.SUCCESS;
} catch (Exception exception) {
Log.e(TAG, "Error cleaning up", exception);
return WorkerResult.FAILURE;
}
}
}
第3步 - 覆盖并实现SaveImageToFileWorker的doWork()
SaveImageToFileWorker
将采取输入和输出。输入是String
与密钥一起存储的KEY_IMAGE_URI
。输出也将String
与密钥一起存储KEY_IMAGE_URI
。
由于这仍然不是关于文件操作的代码库,因此代码如下,有两个TODO
用于填写适当的输入和输出代码的代码。这与您在输入和输出的最后一步中编写的代码非常相似(它使用所有相同的键)。
SaveImageToFileWorker.java
public class SaveImageToFileWorker extends Worker {
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 WorkerResult doWork() {
Context applicationContext = getApplicationContext();
ContentResolver resolver = applicationContext.getContentResolver();
try {
String resourceUri = // TODO get the input Uri from the Data object
Bitmap bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
String imageUrl = MediaStore.Images.Media.insertImage(
resolver, bitmap, TITLE, DATE_FORMATTER.format(new Date()));
if (TextUtils.isEmpty(imageUrl)) {
Log.e(TAG, "Writing to MediaStore failed");
return WorkerResult.FAILURE;
}
// TODO create and set the output Data object with the imageUri.
return WorkerResult.SUCCESS;
} catch (Exception exception) {
Log.e(TAG, "Unable to save image to Gallery", exception);
return WorkerResult.FAILURE;
}
}
}
第4步 - 创建WorkRequest链
您需要修改BlurViewModel
的applyBlur
方法来执行的链条WorkRequest
!而非只有一个。目前代码如下所示:
BlurViewModel.java
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
而不是打电话WorkManager.enqueue()
,打电话WorkManager.beginWith()
。这返回a WorkContinuation
,它定义了一个WorkRequest
s 链。您可以通过调用添加到这个链条的工作请求的then()
方法,例如,如果有三个WorkRequest
对象,workA
,workB
,和workC
,你可以做到以下几点:
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
这将生成并运行以下WorkRequests链:
创建的一个链条CleanupWorker
WorkRequest
,一个BlurImage
WorkRequest
和SaveImageToFile
WorkRequest
在applyBlur
。将输入传递到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();
}
这应该编译并运行。您应该能够看到您选择的任何图像现在模糊保存在图片文件夹中:
第5步 - 重复BlurWorker
是时候添加模糊图像的能力不同了。获取blurLevel
传入的参数applyBlur
并将许多模糊WorkRequest
操作添加到链中。只有第一个WorkRequest
需要并且应该接受uri输入。
请注意,这有点用于学习目的。将三次调用我们的模糊代码的效率低于BlurWorker
控制模糊“级别”的输入。但这是一个很好的做法,并显示了WorkManager链的灵活性。
亲自尝试,然后与下面的代码进行比较:
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();
}
精湛的“工作”!现在,您可以根据需要尽可能多地模糊图像!多神秘啊!