Android には、遅延可能なバックグラウンド処理を行うためのさまざまな方法が用意されています。この Codelab では、遅延可能なバックグラウンド処理用のライブラリで、互換性、柔軟性、シンプルさを兼ね備えた WorkManager を取り上げます。WorkManager は、Android 上で遅延可能な処理を確実に実行するための推奨タスク スケジューラです。
WorkManager とは
WorkManager は Android Jetpack の一部であり、待機的実行と確実な実行というニーズの組み合わせをもつバックグラウンド処理のためのアーキテクチャ コンポーネントです。待機的実行とは、WorkManager がバックグラウンド処理を可能になり次第実行することを指します。確実な実行とは、たとえばアプリを終了した場合など、さまざまな状況下で WorkManager がその処理の開始ロジックを保持して実行することを指します。
WorkManager はシンプルでありながら柔軟性に優れたライブラリで、他にも多くのメリットがあります。以下に例を示します。
- 非同期の 1 回限りのタスクと定期的なタスクの両方をサポート
- ネットワーク状態、保存容量、充電ステータスなどの制約をサポート
- 処理の並列実行など、複雑な処理リクエストのチェーンを作成可能
- 処理リクエストの出力を、後続の処理リクエストの入力として使用可能
- API レベル 14 までの下位互換性(注を参照)
- Google Play 開発者サービスの有無に関係なく動作
- システムの健全性に関するベスト プラクティスを遵守
- UI に処理リクエストのステータスを簡単に表示するための LiveData のサポート
WorkManager の用途
WorkManager ライブラリの使用が適しているのは、ユーザーが特定の画面やアプリを離れた場合でも完了することが求められるタスクです。
WorkManager の使用が適したタスクの例を以下に示します。
- ログのアップロード
- 画像へのフィルタ適用と画像の保存
- ローカルデータとネットワークとの定期的な同期
WorkManager は処理を確実に実行しますが、すべてのタスクがそれを必要とするとは限りません。そのため、メインスレッドから切り離されたタスクすべてに適しているわけではありません。WorkManager の用途について詳しくは、バックグラウンド処理ガイドをご覧ください。
作成するアプリの概要
最近のスマートフォンは、写真撮影の性能が良すぎるくらいです。写ったものがミステリアスに見えるほどぼやけた写真が撮れたのは、過去の話です。
この Codelab では、写真や画像にぼかしを入れて結果をファイルに保存するアプリ、Blur-O-Matic を作成します。ネッシーのような怪物か、おもちゃの潜水艦か、Blur-O-Matic を使えば、誰にもわからなくります。
雑種シマスズキ(米国農務省農業研究局、Peggy Greb 氏撮影) |
学習内容
- プロジェクトへの WorkManager の追加
- 簡単なタスクのスケジュール設定
- 入出力パラメータ
- 処理チェーンの作成
- 一意処理
- 処理ステータスの UI への表示
- 処理のキャンセル
- 処理の制約
必要なもの
- 最新の Android Studio 安定版。
LiveData
とViewModel
に習熟していること。これらのクラスを初めて使用する場合は、Android ライフサイクル対応コンポーネント Codelab(特に ViewModel と LiveData 関連)または 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
***:** 画像を表示し、ぼかしの程度を選択するためのラジオボタンを含むアクティビティ。BlurViewModel
***:** このビューモデルには、BlurActivity
の表示に必要なすべてのデータが格納されています。WorkManager を使用してバックグラウンド処理を開始するクラスでもあります。Constants
**:** Codelab で使用する定数が含まれる静的クラス。SelectImageActivity
**:** 画像を選択するための最初のアクティビティ。res/activity_blur.xml
とres/activity_select.xml
: 各アクティビティのレイアウト ファイル。
***** コードを書き込むのはこの印の付いたファイルのみです。
WorkManager
には、下記の Gradle 依存関係が必要です。これはすでに次のビルドファイルに含まれています。
app/build.gradle
dependencies {
// Other dependencies
implementation "androidx.work:work-runtime:$versions.work"
}
こちらから work-runtime
の最新バージョンを入手し、正しいバージョンを挿入してください。現時点での最新バージョンは下記のとおりです。
build.gradle
versions.work = "2.3.3"
新しいバージョンにアップデートした場合は、必ず [Sync Now] をクリックしてプロジェクトと変更された Gradle ファイルを同期してください。
この手順では、res/drawable
フォルダにある test.jpg
という画像に対して、いくつかの関数をバックグラウンドで実行します。これらの関数により、画像はぼかし加工され、一時ファイルに保存されます。
WorkManager の基礎
把握しておくべき WorkManager クラスとして、以下のものがあります。
Worker
: ここに、バックグラウンドで実行する処理のコードを記述します。このクラスを拡張してdoWork()
メソッドをオーバーライドします。WorkRequest
: 処理実行のリクエストを表します。WorkRequest
の作成の一環としてWorker
を渡します。WorkRequest
を作成する際は、Worker
を実行する場合についてのConstraints
なども指定できます。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();
を返します。- ステップ 2~6 のコードを try / catch ステートメントでラップします。一般的な
Throwable
をキャッチします。 - catch ステートメントで、エラー Log ステートメントを出力します(
Log.e(TAG, "Error applying blur", throwable);
)。 - 続いて
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
には次の 2 種類があります。
OneTimeWorkRequest:
1 回だけ実行するWorkRequest
。PeriodicWorkRequest:
定期的に繰り返すWorkRequest
。
[GO] ボタンが選択されたときに、画像にぼかしを入れるのは 1 回だけです。[GO] ボタンの選択により applyBlur
メソッドが呼び出されるため、そこで BlurWorker
から OneTimeWorkRequest
を作成します。その後、WorkManager
インスタンスを使用して WorkRequest.
をキューに追加します。
次のコード行を BlurViewModel
の applyBlur() メソッドに追加します。
BlurViewModel.java
void applyBlur(int blurLevel) {
mWorkManager.enqueue(OneTimeWorkRequest.from(BlurWorker.class));
}
ステップ 6 - コードを実行する
コードを実行します。[GO] ボタンを選択するとコンパイルされ、通知が表示されます。
(任意)Android Studio で Device File Explorer を開きます。
開いたら、data>data>com.example.background>files>blur_filter_outputs><URI> に移動して、実際に魚にぼかしが入ったことを確認します。
テスト画像にぼかしを入れるところまではできましたが、Blur-O-Matic を実用的な画像編集アプリにするためには、ぼかす画像をユーザーが指定できるようにする必要があります。
そのためには、ユーザーが選択した画像の URI を WorkRequest
への入力として指定します。
ステップ 1 - Data 入力オブジェクトを作成する
入力と出力は、Data
オブジェクトを介して渡されます。Data
オブジェクトは、Key-Value ペアの軽量コンテナです。WorkRequest
とやり取りする可能性のある少量データの格納を目的としています。
ここでバンドルに渡そうとしているのは、ユーザーの画像の URI です。この URI は、mImageUri
という変数に格納されています。
createInputDataForUri
という名前のプライベート メソッドを作成します。このメソッドは以下を動作を行います。
Data.Builder
オブジェクトを作成します。mImageUri
が null 以外の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() を更新する
今度は、Data
オブジェクトから渡された URI を取得するよう、BlurWorker
の doWork()
メソッドを更新しましょう。
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 を出力する
この Worker が完了したら、Result.success()
を返します。出力 Data として outputUri を提供し、以降の処理で他の Worker がこの一時画像を簡単に利用できるようにします。この URI は、次の章で Worker のチェーンを作成する際に役立ちます。手順は次のとおりです。
- 入力の場合と同様に新しい
Data
を作成し、outputUri
をString
として格納します。キーも同じもの(KEY_IMAGE_URI
)を使用します。 - この Data を
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 で Device File Explorer を開き、前の手順で行ったのと同様、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
パッケージに、Worker
を拡張した 2 つの新しいクラスを作成します。
1 つ目を CleanupWorker
、2 つ目を 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 ではないので、入力と出力のコードを正しく記述するという 2 つの 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 のチェーンを作成する
WorkRequest
を単独ではなくチェーンとして実行するには、BlurViewModel
の applyBlur
メソッドを変更する必要があります。現時点でのコードは次のとおりです。
BlurViewModel.java
void applyBlur(int blurLevel) {
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
}
ここで、WorkManager.enqueue()
の代わりに WorkManager.beginWith()
を呼び出します。これにより、WorkRequest
のチェーンを定義する WorkContinuation
が返されます。WorkRequest をこのチェーンに追加するには、then()
を呼び出します。たとえば、workA
、workB
、workC
の 3 つの WorkRequest
オブジェクトがある場合は、次のようにします。
// 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 を繰り返す
次は、画像に程度の異なるぼかしを加える機能を追加します。blurLevel
パラメータを applyBlur
に渡し、その数だけぼかし処理の 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 のもう一つの強力な機能である一意処理チェーンに取り組みましょう。
実行する処理チェーンを一度に 1 つにしたい場合があります。たとえば、ローカルデータとサーバーを同期する処理チェーンなら、最初のデータ同期が終わってから 2 回目を開始するのが望ましいでしょう。そのためには、beginWith
の代わりに beginUniqueWork
を使用し、一意の 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 がぼかしを入れる画像は一度に 1 つのみになりました。
このセクションには LiveData が何度も出てくるため、内容を完全に把握するには LiveData に習熟している必要があります。LiveData は、ライフサイクルを認識する監視可能なデータホルダーです。
LiveData や監視可能オブジェクトを初めて使用する場合は、ドキュメントまたは Android ライフサイクル対応コンポーネント Codelab をご確認ください。
次に行う大きな変更は、処理実行時にアプリに表示される内容を実際に変更することです。
WorkInfo
オブジェクトを保持する LiveData
を取得することにより、任意の WorkRequest
のステータスを取得できます。WorkInfo
は、WorkRequest
の現在のステータスに関する以下の詳細情報を含むオブジェクトです。
次の表に、LiveData<WorkInfo>
オブジェクトまたは LiveData<List<WorkInfo>>
オブジェクトを取得する 3 種類の方法を、それぞれの説明とともに示します。
種類 | 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
を取得できます。
LiveData<List<WorkInfo>>
の新しい変数mSavedWorkInfo
を宣言します。BlurViewModel
コンストラクタで、WorkManager.getWorkInfosByTagLiveData
を使用してWorkInfo
を取得します。mSavedWorkInfo
のゲッターを追加します。
必要なコードは以下のとおりです。
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
は 1 つのみになります。 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
オブジェクトを取得できます。ぼかしを入れた画像が準備できたら、[SEE FILE] ボタンを表示しましょう。
ステップ 1 - mOutputUri を作成する
BlurViewModel
で最終 URI 用の変数を作成し、そのゲッターとセッターを指定します。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 - SEE FILE ボタンを作成する
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
を表示するとともにビューモデルのsetOutputUri
をこの URI を使って呼び出します。
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 - コードを実行する
コードを実行します。[SEE FILE] ボタンが新たに表示され、選択すると出力ファイルが開くはずです。
[CANCEL WORK] ボタンを追加したので、これを機能させるコードも追加しましょう。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
の UI への表示WorkRequest
のキャンセルWorkRequest
への制約の追加
本当におつかれさまでした。最終状態のコードとすべての変更を確認するには、以下をご覧ください。
または、GitHub から WorkManager の Codelab のクローンを作成することもできます。
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
WorkManager は、この Codelab で取り上げたもの以外にも、繰り返し処理、テスト支援ライブラリ、並列処理リクエスト、入力マージツールなど、多くの機能をサポートしています。詳しくは、WorkManager のドキュメントをご覧ください。