1. はじめに
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 を使えば、誰にもわからなくります。
  | 
  | 
学習内容
- プロジェクトへの WorkManager の追加
 - 簡単なタスクのスケジュール設定
 - 入出力パラメータ
 - 処理チェーンの作成
 - 一意処理
 - 処理ステータスの UI への表示
 - 処理のキャンセル
 - 処理の制約
 
必要なもの
- 最新の Android Studio 安定版。
 LiveDataとViewModelに習熟していること。これらのクラスを初めて使用する場合は、Android ライフサイクル対応コンポーネント Codelab(特に ViewModel と LiveData 関連)または Room とビュー Codelab(アーキテクチャ コンポーネントの概要)をご確認ください。
2. 設定方法
ステップ 1 - コードをダウンロードする
次のリンクをクリックして、この Codelab 用のコードすべてをダウンロードします。
必要に応じて、GitHub から WorkManager Codelab のクローンを作成することもできます。
$ git clone -b start_kotlin https://github.com/googlecodelabs/android-workmanager
ステップ 2 - アプリを実行する
アプリを実行します。次の画面が表示されます。

この画面では、ラジオボタンで画像をどの程度ぼかすかを選択できます。[GO] ボタンを選択すると、最終的に画像がぼかし加工されて保存されます。
上の写真では、まだぼかしは適用されていません。
初期状態のコードには以下が含まれています。
WorkerUtils: このクラスには、実際に画像にぼかしを入れるコードと、後でNotificationsを表示したり、ビットマップをファイルに保存したり、アプリを遅らせたりするのに使用するいくつかのメソッドが含まれています。BlurActivity:* 画像を表示し、ぼかしの程度を選択するためのラジオボタンを含むアクティビティ。BlurViewModel:* このビューモデルには、BlurActivityの表示に必要なすべてのデータが格納されています。WorkManager を使用してバックグラウンド処理を開始するクラスでもあります。Constants: Codelab で使用する定数が含まれる静的クラス。res/activity_blur.xml:BlurActivityのレイアウト ファイル。
***** コードを書き込むのはこの印の付いたファイルのみです。
3. アプリに WorkManager を追加する
WorkManager には、下記の Gradle 依存関係が必要です。これはすでに次のビルドファイルに含まれています。
app/build.gradle
dependencies {
    // WorkManager dependency
    implementation "androidx.work:work-runtime-ktx:$versions.work"
}
こちらから work-runtime-ktx の最新の安定バージョンを入手し、正しいバージョンを挿入してください。現時点での最新バージョンは下記のとおりです。
build.gradle
versions.work = "2.7.1"
新しいバージョンにアップデートした場合は、必ず [Sync Now] をクリックしてプロジェクトと変更された Gradle ファイルを同期してください。
4. 最初の WorkRequest を作成する
この手順では、res/drawable フォルダにある android_cupcake.png という画像に対して、いくつかの関数をバックグラウンドで実行します。これらの関数により、画像はぼかし加工され、一時ファイルに保存されます。
WorkManager の基礎
把握しておくべき WorkManager クラスとして、以下のものがあります。
Worker: ここに、バックグラウンドで実行する処理のコードを記述します。このクラスを拡張してdoWork()メソッドをオーバーライドします。WorkRequest: 処理実行のリクエストを表します。WorkRequestの作成の一環としてWorkerを渡します。WorkRequestを作成する際は、Workerを実行する場合についてのConstraintsなども指定できます。WorkManager: このクラスが実際にWorkRequestをスケジュールして実行します。指定された制約を尊重しながら、負荷がシステム リソースに分散されるようWorkRequestをスケジュールします。
今回は、画像にぼかしを入れるコードを含んだ BlurWorker を新たに定義します。[GO] ボタンを選択すると、WorkRequest が作成されて WorkManager によりキューに追加されるようにします。
ステップ 1 - BlurWorker を作成する
workers パッケージで、新しい Kotlin クラス BlurWorker を作成します。
ステップ 2 - コンストラクタを追加する
次のようにして、BlurWorker クラスに Worker の依存関係を追加します。
class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
}
ステップ 3 - doWork() をオーバーライドして実装する
Worker は、表示されるカップケーキの画像にぼかしを入れます。
処理の進行状況を表示するために、WorkerUtil の makeStatusNotification() を使用します。このメソッドを使用すると、画面上部に通知バナーを簡単に表示できます。
doWork() メソッドをオーバーライドし、以下のように実装します。完成したコードは、このセクションの最後で参照できます。
applicationContextプロパティを呼び出してContextを取得します。これをappContextという名前の新しいvalに代入します。これは、この後実行するさまざまなビットマップ操作で必要になります。- 関数 
makeStatusNotificationを使用してステータス通知を表示し、画像のぼかしについてユーザーに通知します。 - カップケーキの画像から 
Bitmapを作成します。 
val picture = BitmapFactory.decodeResource(
        appContext.resources,
        R.drawable.android_cupcake)
WorkerUtilsのblurBitmapメソッドを呼び出して、ぼかしの入ったビットマップを取得します。WorkerUtilsのwriteBitmapToFileメソッドを呼び出して、このビットマップを一時ファイルに書き込みます。返される URI をローカル変数に保存します。WorkerUtilsのmakeStatusNotificationメソッドを呼び出して、URI を表示する通知を作成します。Result.success()を返します。- ステップ 3~6 のコードを try / catch ステートメントでラップします。一般的な 
Throwableをキャッチします。 - catch ステートメント内で、次のような Log ステートメントを使用してエラー メッセージを出力します: 
Log.e(TAG, "Error applying blur") - 続いて 
Result.failure()を返します。 
このステップの完成版のコードを以下に示します。
**BlurWorker.**kt
package com.example.background.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.R
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
    override fun doWork(): Result {
        val appContext = applicationContext
        makeStatusNotification("Blurring image", appContext)
        return try {
            val picture = BitmapFactory.decodeResource(
                    appContext.resources,
                    R.drawable.android_cupcake)
            val output = blurBitmap(picture, appContext)
            // Write bitmap to a temp file
            val outputUri = writeBitmapToFile(appContext, output)
            makeStatusNotification("Output is $outputUri", appContext)
            Result.success()
        } catch (throwable: Throwable) {
            Log.e(TAG, "Error applying blur")
            Result.failure()
        }
    }
}
ステップ 4 - ViewModel 内で WorkManager を取得する
ViewModel 内で WorkManager インスタンスのクラス変数を作成します。
BlurViewModel.kt
private val workManager = WorkManager.getInstance(application)
ステップ 5 - WorkManager のキューに WorkRequest を追加する
それでは、WorkRequest を作成して WorkManager に実行させましょう。WorkRequest には次の 2 種類があります。
OneTimeWorkRequest: 1 回だけ実行されるWorkRequest。PeriodicWorkRequest: 定期的に繰り返されるWorkRequest。
[GO] ボタンが選択されたときに、画像にぼかしを入れるのは 1 回だけです。[GO] ボタンの選択により applyBlur メソッドが呼び出されるため、そこで BlurWorker から OneTimeWorkRequest を作成します。その後、WorkManager インスタンスを使用して WorkRequest. をキューに追加します。
次のコード行を BlurViewModel's applyBlur() メソッドに追加します。
BlurViewModel.kt
internal fun applyBlur(blurLevel: Int) {
   workManager.enqueue(OneTimeWorkRequest.from(BlurWorker::class.java))
}
ステップ 6 - コードを実行する
コードを実行します。コンパイルが行われ、[GO] ボタンを選択すると通知が表示されます。ぼかしをより強くするには、[More blurred] または [The moset blurred] オプションを選択します。

画像のぼかし処理が正しく行われたかどうかを確認するには、Android Studio で Device File Explorer を開きます。

次に、[data] > [data] > [com.example.background] > [files] > [blur_filter_outputs] > <URI> の順に移動して、実際にカップケーキにぼかしが入ったことを確認します。

5. 入力と出力を追加する
リソース ディレクトリ内の画像アセットにぼかしを入れることができました。しかし、Blur-O-Matic をより優れた画像編集アプリにするには、画面に表示されている画像にぼかしを入れて、その結果を画面で確認できるようにする必要があります。
そのためには、表示されるカップケーキの画像の URI を WorkRequest の入力として指定し、WorkRequest の出力を使って、ぼかしを入れた最終的な画像を表示します。
ステップ 1 - Data 入力オブジェクトを作成する
入力と出力は、Data オブジェクトを介して渡されます。Data オブジェクトは、Key-Value ペアの軽量コンテナです。WorkRequest とやり取りする可能性のある少量データの格納を目的としています。
ここでバンドルに渡そうとしているのは、ユーザーの画像の URI です。この URI は、imageUri という変数に格納されています。
BlurViewModel 内に、createInputDataForUri というプライベート メソッドを作成します。このメソッドは以下の動作を行います。
Data.Builderオブジェクトを作成します。リクエストされたら、androidx.work.Dataをインポートします。imageUriが null 以外のURIの場合は、putStringメソッドを使用してそれをDataオブジェクトに追加します。このメソッドはキーと値を受け取ります。Constantsクラスの文字列定数KEY_IMAGE_URIを使用できます。Data.Builderオブジェクトに対してbuild()を呼び出し、Dataオブジェクトを作成して返します。
完成した createInputDataForUri メソッドを以下に示します。
BlurViewModel.kt
/**
 * Creates the input data bundle which includes the Uri to operate on
 * @return Data which contains the Image Uri as a String
 */
private fun createInputDataForUri(): Data {
    val builder = Data.Builder()
    imageUri?.let {
        builder.putString(KEY_IMAGE_URI, imageUri.toString())
    }
    return builder.build()
}
ステップ 2 - Data オブジェクトを WorkRequest に渡す
BlurViewModel の applyBlur メソッドを変更して、次のことを行うようにします。
- 新しい 
OneTimeWorkRequestBuilderを作成します。 setInputDataを呼び出し、createInputDataForUriからの結果を渡します。OneTimeWorkRequestを作成します。WorkManagerリクエストを使用して処理リクエストをキューに登録します。これにより処理の実行がスケジュールされます。
完成した applyBlur メソッドを以下に示します。
BlurViewModel.kt
internal fun applyBlur(blurLevel: Int) {
    val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
            .setInputData(createInputDataForUri())
            .build()
    workManager.enqueue(blurRequest)
}
ステップ 3 - 入力を取得するよう BlurWorker の doWork() を更新する
今度は、Data オブジェクトから渡された URI を取得するよう、BlurWorker の doWork() メソッドを更新しましょう。
BlurWorker.kt
override fun doWork(): Result {
    val appContext = applicationContext
    // ADD THIS LINE
    val resourceUri = inputData.getString(KEY_IMAGE_URI)
    // ... rest of doWork()
}
ステップ 4 - 指定された URI の画像にぼかしを入れる
URI を使用して、画面に表示されるカップケーキの画像にぼかしを入れましょう。
- 画像リソースを取得していた以前のコードを削除します。
 
val picture = BitmapFactory.decodeResource(appContext.resources, R.drawable.android_cupcake)
- 渡された 
Dataから取得したresourceUriが空でないことを確認します。 - 渡された画像を 
picture変数に代入します。 
val picture = BitmapFactory.decodeStream(
appContext.contentResolver.
  `openInputStream(Uri.parse(resourceUri)))`
BlurWorker.kt
override fun doWork(): Result {
    val appContext = applicationContext
    val resourceUri = inputData.getString(KEY_IMAGE_URI)
    makeStatusNotification("Blurring image", appContext)
    return try {
        // REMOVE THIS
        //    val picture = BitmapFactory.decodeResource(
        //            appContext.resources,
        //            R.drawable.android_cupcake)
        if (TextUtils.isEmpty(resourceUri)) {
            Log.e(TAG, "Invalid input uri")
            throw IllegalArgumentException("Invalid input uri")
        }
        val resolver = appContext.contentResolver
        val picture = BitmapFactory.decodeStream(
                resolver.openInputStream(Uri.parse(resourceUri)))
        val output = blurBitmap(picture, appContext)
        // Write bitmap to a temp file
        val outputUri = writeBitmapToFile(appContext, output)
        Result.success()
    } catch (throwable: Throwable) {
        Log.e(TAG, "Error applying blur")
        throwable.printStackTrace()
        Result.failure()
    }
}
ステップ 5 - 一時画像用 URI を出力する
この Worker での作業が完了し、Result.success() で出力 URI を返すことができるようになりました。出力 URI を出力データとして渡し、以降の処理で他のワーカーがこの一時画像を簡単に利用できるようにします。これは、次の章でワーカーのチェーンを作成する際に役立ちます。方法は次のとおりです。
- 入力の場合と同様に新しい 
Dataを作成し、outputUriをStringとして格納します。キーも同じもの(KEY_IMAGE_URI)を使用します。 - この Data を、
Result.success(Data outputData)メソッドを使用して WorkManager に返します。 
BlurWorker.kt
doWork() 内の Result.success() の行を次のように変更します。
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
Result.success(outputData)
ステップ 6 - アプリを実行する
この時点でアプリを実行します。アプリがコンパイルされます。ぼかしを入れた画像は Device File Explorer からは表示できても、画面にはまだ表示されないという同じ動作になるはずです。
ぼかしを入れた画像を確認するには、Android Studio で Device File Explorer を開き、前のステップと同様に data/data/com.example.background/files/blur_filter_outputs/<URI> に移動します。
なお、画像を表示するには、[Synchronize] が必要な場合があります。

おつかれさまでした。WorkManager を使用して入力画像にぼかしを入れることができました。
6. 処理のチェーンを作成する
現時点で行っているのは、画像にぼかしを入れるという処理のみです。たしかにこれがなくては始まりませんが、まだ以下のように重要な機能が欠けています。
- 一時ファイルがクリーンアップされません。
 - 画像が永続ファイルに保存されません。
 - 写真に常に同程度のぼかししか入れられません。
 
ここでは、WorkManager の処理チェーンを使用して上記の機能を追加します。
WorkManager を使用すると、個別に作成した WorkerRequest を順次または並列に実行できます。この手順では、下図のような処理チェーンを作成します。

それぞれの箱は WorkRequest を表します。
チェーンのもう一つ便利な特長は、WorkRequest の出力を後続の WorkRequest の入力にできるという点です。以下、各 WorkRequest 間の入出力を青色のテキストで示します。
ステップ 1 - クリーンアップ用と保存用の Worker を作成する
まず、必要な Worker クラスをすべて定義します。画像にぼかしを入れる Worker はすでにありますが、一時ファイルをクリーンアップする Worker と、画像を永続的に保存する Worker も必要です。
workers パッケージに、Worker を拡張した 2 つの新しいクラスを作成します。
1 つ目を CleanupWorker、2 つ目を SaveImageToFileWorker とします。
ステップ 2 - Worker を継承する
Worker クラスを継承した CleanupWorker クラスを作成します。必要なコンストラクタ パラメータを追加します。
class CleanupWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
}
ステップ 3 - CleanupWorker の doWork() をオーバーライドして実装する
CleanupWorker には、入力も出力も必要ありません。一時ファイルが存在する場合に、常にそれを削除します。ファイル操作はこの Codelab の範囲外ですので、以下の CleanupWorker のコードをコピーしてかまいません。
CleanupWorker.kt
package com.example.background.workers
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.OUTPUT_PATH
import java.io.File
/**
 * Cleans up temporary files generated during blurring process
 */
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
    override fun doWork(): Result {
        // Makes a notification when the work starts and slows down the work so that
        // it's easier to see each WorkRequest start, even on emulated devices
        makeStatusNotification("Cleaning up old temporary files", applicationContext)
        sleep()
        return try {
            val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
            if (outputDirectory.exists()) {
                val entries = outputDirectory.listFiles()
                if (entries != null) {
                    for (entry in entries) {
                        val name = entry.name
                        if (name.isNotEmpty() && name.endsWith(".png")) {
                            val deleted = entry.delete()
                            Log.i(TAG, "Deleted $name - $deleted")
                        }
                    }
                }
            }
            Result.success()
        } catch (exception: Exception) {
            exception.printStackTrace()
            Result.failure()
        }
    }
}
ステップ 4 - SaveImageToFileWorker の doWork() をオーバーライドして実装する
SaveImageToFileWorker は入力を受け取り、出力を渡します。入力は、ぼかしを入れた一時画像の URI の String であり、キー KEY_IMAGE_URI を使用して格納されます。出力も、保存済みのぼかしを入れた画像の URI の String であり、キー KEY_IMAGE_URI を使用して格納されます。

これはファイル操作に関する Codelab ではないので、コードを以下に示します。resourceUri と output の値がキー KEY_IMAGE_URI を使用してどのように取得されるかに注目してください。これは、前の手順で入出力のために作成したコードとよく似ています(使用するキーはまったく同じです)。
SaveImageToFileWorker.kt
package com.example.background.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.workDataOf
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.KEY_IMAGE_URI
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
 * Saves the image to a permanent file
 */
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
    private val title = "Blurred Image"
    private val dateFormatter = SimpleDateFormat(
            "yyyy.MM.dd 'at' HH:mm:ss z",
            Locale.getDefault()
    )
    override fun doWork(): Result {
        // Makes a notification when the work starts and slows down the work so that
        // it's easier to see each WorkRequest start, even on emulated devices
        makeStatusNotification("Saving image", applicationContext)
        sleep()
        val resolver = applicationContext.contentResolver
        return try {
            val resourceUri = inputData.getString(KEY_IMAGE_URI)
            val bitmap = BitmapFactory.decodeStream(
                    resolver.openInputStream(Uri.parse(resourceUri)))
            val imageUrl = MediaStore.Images.Media.insertImage(
                    resolver, bitmap, title, dateFormatter.format(Date()))
            if (!imageUrl.isNullOrEmpty()) {
                val output = workDataOf(KEY_IMAGE_URI to imageUrl)
                Result.success(output)
            } else {
                Log.e(TAG, "Writing to MediaStore failed")
                Result.failure()
            }
        } catch (exception: Exception) {
            exception.printStackTrace()
            Result.failure()
        }
    }
}
ステップ 5 - BlurWorker の通知を変更する
これで、適切なフォルダへの画像の保存を担う Worker のチェーンができました。次に、エミュレータ デバイスでも各 WorkRequest の開始を容易に確認できるよう、WorkerUtils クラスで定義されている sleep() メソッドを使用して処理速度を遅くします。BlurWorker の最終版は、次のようになります。
BlurWorker.kt
class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
override fun doWork(): Result {
    val appContext = applicationContext
    val resourceUri = inputData.getString(KEY_IMAGE_URI)
    makeStatusNotification("Blurring image", appContext)
    // ADD THIS TO SLOW DOWN THE WORKER
    sleep()
    // ^^^^
    return try {
        if (TextUtils.isEmpty(resourceUri)) {
            Timber.e("Invalid input uri")
            throw IllegalArgumentException("Invalid input uri")
        }
        val resolver = appContext.contentResolver
        val picture = BitmapFactory.decodeStream(
                resolver.openInputStream(Uri.parse(resourceUri)))
        val output = blurBitmap(picture, appContext)
        // Write bitmap to a temp file
        val outputUri = writeBitmapToFile(appContext, output)
        val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
        Result.success(outputData)
    } catch (throwable: Throwable) {
        throwable.printStackTrace()
        Result.failure()
    }
}
ステップ 6 - WorkRequest のチェーンを作成する
WorkRequest を単独ではなくチェーンとして実行するには、BlurViewModel の applyBlur メソッドを変更する必要があります。現時点でのコードは次のとおりです。
BlurViewModel.kt
val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
        .setInputData(createInputDataForUri())
        .build()
workManager.enqueue(blurRequest)
ここで、workManager.enqueue() の代わりに workManager.beginWith() を呼び出します。これにより、WorkRequest のチェーンを定義する WorkContinuation が返されます。WorkRequest をこのチェーンに追加するには、then() を呼び出します。たとえば、workA、workB、workC の 3 つの WorkRequest オブジェクトがある場合は、次のようにします。
// Example code, don't copy to the project
val continuation = workManager.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.kt
internal fun applyBlur(blurLevel: Int) {
    // Add WorkRequest to Cleanup temporary images
    var continuation = workManager
            .beginWith(OneTimeWorkRequest
            .from(CleanupWorker::class.java))
    // Add WorkRequest to blur the image
    val blurRequest = OneTimeWorkRequest.Builder(BlurWorker::class.java)
            .setInputData(createInputDataForUri())
            .build()
    continuation = continuation.then(blurRequest)
    // Add WorkRequest to save the image to the filesystem
    val save = OneTimeWorkRequest.Builder(SaveImageToFileWorker::class.java).build()
    continuation = continuation.then(save)
    // Actually start the work
    continuation.enqueue()
}
これを コンパイルして実行します。[Go] ボタンを押すと、実行中のさまざまなワーカーの通知が表示されます。ここでも、ぼかしを入れた画像は Device File Explorer で確認できます。次のステップでは、ぼかしを入れた画像をデバイスで確認できるように、別のボタンを追加します。
以下のスクリーンショットでは、現在実行されているワーカーを示す通知メッセージが表示されています。
  

ステップ 7 - BlurWorker を繰り返す
次は、画像に程度の異なるぼかしを加える機能を追加します。blurLevel パラメータを applyBlur に渡し、その数だけぼかし処理の WorkRequest をチェーンに追加します。最初の WorkRequest のみが URI の入力を必要とします。
自分でコードを追加してみてから、以下のコードと比較してください。
BlurViewModel.kt
internal fun applyBlur(blurLevel: Int) {
    // Add WorkRequest to Cleanup temporary images
    var continuation = workManager
            .beginWith(OneTimeWorkRequest
            .from(CleanupWorker::class.java))
    // Add WorkRequests to blur the image the number of times requested
    for (i in 0 until blurLevel) {
        val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
        // 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
    val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
            .build()
    continuation = continuation.then(save)
    // Actually start the work
    continuation.enqueue()
}
Device File Explorer を開き、ぼかしを入れた画像を確認します。出力フォルダには、ぼかしが入った複数の画像が保存されています。それらは、ぼかし処理の中間ステージの画像と、選択したぼかし量に基づいてぼかしを入れた最終画像です。
おつかれさまでした。これで、ぼかしの度合いを選択できるようになりました。ミステリアスな画像を作成できます。
7. 処理チェーンを一意にする
チェーンを使えるようになったので、次は WorkManager のもう一つの強力な機能である一意処理チェーンに取り組みましょう。
実行する処理チェーンを一度に 1 つにしたい場合があります。たとえば、ローカルデータとサーバーを同期する処理チェーンなら、最初のデータ同期が終わってから 2 回目を開始するのが望ましいでしょう。そのためには、beginWith の代わりに beginUniqueWork を使用し、一意の String の名前を付けます。これにより、処理リクエストのチェーン全体に名前が付き、まとめて参照やクエリができるようになります。
それでは、beginUniqueWork を使用してファイルにぼかしを入れる処理チェーンを一意なものにします。キーとして IMAGE_MANIPULATION_WORK_NAME を渡します。ExistingWorkPolicy も渡す必要があります。指定できるオプションは REPLACE、KEEP、APPEND のいずれかです。
ここでは REPLACE を使用します。これは、ユーザーが現在のぼかし処理の終了を待たずに他の画像の処理を始めた場合、現在の処理が停止されて新しい画像のぼかし処理が開始されるようにするためです。
一意の連続した処理を開始するコードを以下に示します。
BlurViewModel.kt
// REPLACE THIS CODE:
// var continuation = workManager
//            .beginWith(OneTimeWorkRequest
//            .from(CleanupWorker::class.java))
// WITH
var continuation = workManager
        .beginUniqueWork(
                IMAGE_MANIPULATION_WORK_NAME,
                ExistingWorkPolicy.REPLACE,
                OneTimeWorkRequest.from(CleanupWorker::class.java)
        )
これで、Blur-O-Matic がぼかしを入れる画像は一度に 1 つのみになりました。
8. 処理にタグを付けてステータスを表示する
このセクションには LiveData が何度も出てくるため、内容を完全に把握するには LiveData に習熟している必要があります。LiveData は、ライフサイクルを認識する監視可能なデータホルダーです。
LiveData や監視可能オブジェクトを初めて使用する場合は、ドキュメントまたは Android ライフサイクル対応コンポーネント Codelab をご確認ください。
次に行う大きな変更は、処理実行時にアプリに表示される内容を実際に変更することです。
WorkInfo オブジェクトを保持する LiveData を取得することにより、任意の WorkRequest のステータスを取得できます。WorkInfo は、WorkRequest の現在のステータスに関する以下の詳細情報を含むオブジェクトです。
次の表に、LiveData<WorkInfo> オブジェクトまたは LiveData<List<WorkInfo>> オブジェクトを取得する 3 種類の方法を、それぞれの説明とともに示します。
種類  | WorkManager のメソッド  | 説明  | 
ID を使用した処理の取得  | 
  | 各   | 
一意のチェーン名を使用した処理の取得  | 
  | 前述のとおり、  | 
タグを使用した処理の取得  | 
  | 任意の WorkRequest には、必要に応じて文字列のタグを付けることができます。複数の   | 
ここでは、SaveImageToFileWorker の WorkRequest にタグを付けて、getWorkInfosByTag を使用して取得できるようにします。WorkManager ID を使用する代わりに処理にタグを付けるのは、ユーザーが複数の画像にぼかしを入れる場合、画像保存 WorkRequest のすべてに共通するのは、ID ではなくタグになるためです。また、タグは選択することもできます。
getWorkInfosForUniqueWork を使用しないのは、これによりすべてのぼかしの WorkRequest とクリーンアップの WorkRequest の WorkInfo まで返され、画像保存 WorkRequest を特定するには追加のロジックが必要になるためです。
ステップ 1 - 処理にタグを付ける
applyBlur で SaveImageToFileWorker を作成するときに、String 定数 TAG_OUTPUT を使用して処理にタグを付けます。
BlurViewModel.kt
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
        .addTag(TAG_OUTPUT) // <-- ADD THIS
        .build()
ステップ 2 - WorkInfo を取得する
処理にタグが付いたので、WorkInfo を取得できます。
BlurViewModelで新しいクラス変数outputWorkInfosを宣言します。これはLiveData<List<WorkInfo>>です。BlurViewModelに、WorkManager.getWorkInfosByTagLiveDataを使用してWorkInfoを取得する init ブロックを追加します。
必要なコードは以下のとおりです。
BlurViewModel.kt
// New instance variable for the WorkInfo
internal val outputWorkInfos: LiveData<List<WorkInfo>>
// Modify the existing init block in the BlurViewModel class to this:
init {
    imageUri = getImageUri(application.applicationContext)
    // This transformation makes sure that whenever the current work Id changes the WorkInfo
    // the UI is listening to changes
    outputWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
}
ステップ 3 - WorkInfo を表示する
WorkInfo の LiveData を取得できるようになったので、BlurActivity でそれを監視できます。オブザーバーで以下の処理を行います。
WorkInfoのリストが null でなく、WorkInfoオブジェクトが含まれていることを確認します。含まれていない場合は、まだ [GO] ボタンが選択されていないため戻ります。- リストの最初の 
WorkInfoを取得します。処理チェーンを一意にしたため、TAG_OUTPUTでタグ付けされたWorkInfoは 1 つのみになります。 workInfo.state.isFinishedを使用して、処理ステータスが終了済みかどうかを確認します。- 終了済みでない場合は、
showWorkInProgress()を呼び出します。これにより [Go] ボタンが非表示になり、[Cancel Work] ボタンと進行状況バーが表示されます。 - 終了済みの場合は、
showWorkFinished()を呼び出します。これにより、[Cancel Work] ボタンと進行状況バーが非表示になり、[Go] ボタンが表示されます。 
以下にコードを示します。
注: リクエストされたら、androidx.lifecycle.Observer をインポートします。
BlurActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    // Observe work status, added in onCreate()
    viewModel.outputWorkInfos.observe(this, workInfosObserver())
}
// Define the observer function
private fun workInfosObserver(): Observer<List<WorkInfo>> {
    return Observer { listOfWorkInfo ->
        // Note that these next few lines grab a single WorkInfo if it exists
        // This code could be in a Transformation in the ViewModel; they are included here
        // so that the entire process of displaying a WorkInfo is in one location.
        // If there are no matching work info, do nothing
        if (listOfWorkInfo.isNullOrEmpty()) {
            return@Observer
        }
        // We only care about the one output status.
        // Every continuation has only one worker tagged TAG_OUTPUT
        val workInfo = listOfWorkInfo[0]
        if (workInfo.state.isFinished) {
            showWorkFinished()
        } else {
            showWorkInProgress()
        }
    }
}
ステップ 4 - アプリを実行する
アプリを実行します。コンパイルされて、実行が開始されます。処理中は進行状況バーとキャンセル ボタンが表示されます。

9. 最終出力を表示する
各 WorkInfo には getOutputData メソッドもあり、最後に保存された画像を含む出力 Data オブジェクトを取得できます。Kotlin では、言語により生成される変数 outputData を使用してこのメソッドにアクセスできます。ぼかしを入れた画像が準備できたら、[SEE FILE] ボタンを表示しましょう。
ステップ 1 - [SEE FILE] ボタンを作成する
activity_blur.xml レイアウトには非表示のボタンがすでに存在します。BlurActivity にある outputButton です。
BlurActivity の onCreate() の中で、このボタンのクリック リスナーを設定します。このリスナーでは、URI を取得し、その URI を表示するアクティビティを開きます。以下のコードを使用できます。
BlurActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
   // Setup view output image file button
   binding.seeFileButton.setOnClickListener {
       viewModel.outputUri?.let { currentUri ->
           val actionView = Intent(Intent.ACTION_VIEW, currentUri)
           actionView.resolveActivity(packageManager)?.run {
               startActivity(actionView)
           }
       }
   }
}
ステップ 2 - URI を設定してボタンを表示する
実際にボタンを機能させるには、以下のように WorkInfo オブザーバーの最終調整を行う必要があります。
WorkInfoが終了済みになったら、workInfo.outputDataを使用して出力データを取得します。- 出力 URI を取得します。
Constants.KEY_IMAGE_URIキーを使用して格納されていることを思い出してください。 - URI が空でなければ正しく保存が行われているため、
outputButtonを表示するとともにビューモデルのsetOutputUriをこの URI を使って呼び出します。 
BlurActivity.kt
private fun workInfosObserver(): Observer<List<WorkInfo>> {
    return Observer { listOfWorkInfo ->
        // Note that these next few lines grab a single WorkInfo if it exists
        // This code could be in a Transformation in the ViewModel; they are included here
        // so that the entire process of displaying a WorkInfo is in one location.
        // If there are no matching work info, do nothing
        if (listOfWorkInfo.isNullOrEmpty()) {
            return@Observer
        }
        // We only care about the one output status.
        // Every continuation has only one worker tagged TAG_OUTPUT
        val workInfo = listOfWorkInfo[0]
        if (workInfo.state.isFinished) {
            showWorkFinished()
            // Normally this processing, which is not directly related to drawing views on
            // screen would be in the ViewModel. For simplicity we are keeping it here.
            val outputImageUri = workInfo.outputData.getString(KEY_IMAGE_URI)
            // If there is an output file show "See File" button
            if (!outputImageUri.isNullOrEmpty()) {
                viewModel.setOutputUri(outputImageUri)
                binding.seeFileButton.visibility = View.VISIBLE
            }
        } else {
            showWorkInProgress()
        }
    }
}
ステップ 3 - コードを実行する
コードを実行します。[SEE FILE] ボタンが新たに表示され、選択すると出力ファイルが開くはずです。


10. 処理をキャンセルする

[CANCEL WORK] ボタンを追加したので、これを機能させるコードも追加しましょう。WorkManager で処理をキャンセルするには、ID、タグ、一意のチェーン名を使用できます。
今回はキャンセルする処理の指定に一意のチェーン名を使用します。キャンセル対象がチェーン内の特定のステップではなく、すべての処理だからです。
ステップ 1 - 名前を指定して処理をキャンセルする
BlurViewModel に、一意の処理をキャンセルするための新しいメソッド cancelWork() を追加します。この関数の中で workManager の cancelUniqueWork を呼び出し、タグ IMAGE_MANIPULATION_WORK_NAME を渡します。
BlurViewModel.kt
internal fun cancelWork() {
    workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
}
ステップ 2 - キャンセル メソッドを呼び出す
cancelButton ボタンで cancelWork が呼び出されるようにします。
BlurActivity.kt
// In onCreate()
// Hookup the Cancel button
binding.cancelButton.setOnClickListener { viewModel.cancelWork() }
ステップ 3 - 処理を実行してキャンセルする
アプリを実行します。正常にコンパイルされるはずです。画像のぼかしを開始したら、キャンセル ボタンを選択します。チェーン全体がキャンセルされます。

処理がキャンセルされると、WorkState は FINISHED 状態でなくなるため、GO ボタンのみが表示されるようになります。
11. 処理の制約
最後に、WorkManager は Constraints をサポートしていることを忘れてはいけません。Blur-O-Matic では、デバイスが充電中でなければならないという制約を使用します。つまり、作業リクエストが実行されるのはデバイスが充電中の場合のみです。
ステップ 1 - 充電の制約を作成して追加する
Constraints オブジェクトを作成するには、Constraints.Builder を使用します。次に、以下に示すように setRequiresCharging() メソッドを使用して必要な制約を設定し、WorkRequest に追加します。
リクエストされたら、androidx.work.Constraints をインポートします。
BlurViewModel.kt
// Put this inside the applyBlur() function, above the save work request.
// Create charging constraint
val constraints = Constraints.Builder()
        .setRequiresCharging(true)
        .build()
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
        .setConstraints(constraints)
        .addTag(TAG_OUTPUT)
        .build()
continuation = continuation.then(save)
// Actually start the work
continuation.enqueue()
ステップ 2 - エミュレータまたはデバイスでテストする
Blur-O-Matic を実行できるようになりました。デバイスを使用している場合は、電源を切断または接続します。エミュレータを使用している場合は、下図のように [Extended controls] ウィンドウで充電ステータスを変更できます。

デバイスが充電中でない場合、電源に接続するまで SaveImageToFileWorker, の実行は停止されます。

12. 完了
これで、Blur-O-Matic アプリが完成しました。このプロセスでは以下について学びました。
- プロジェクトへの WorkManager の追加
 OneTimeWorkRequestのスケジュール設定- 入出力パラメータ
 - 処理チェーンによる 
WorkRequestの連結 - 一意の 
WorkRequestチェーンの命名 WorkRequestへのタグ付けWorkInfoの UI への表示WorkRequestのキャンセルWorkRequestへの制約の追加
本当におつかれさまでした。最終状態のコードとすべての変更を確認するには、以下をご覧ください。
または、GitHub から WorkManager の Codelab のクローンを作成することもできます。
$ git clone https://github.com/googlecodelabs/android-workmanager
WorkManager は、この Codelab で取り上げたもの以外にも、繰り返し処理、テスト支援ライブラリ、並列処理リクエスト、入力マージツールなど、多くの機能をサポートしています。詳しくは、WorkManager のドキュメントをご覧いただくか、高度な WorkManager の Codelab に進んでください。
  ![ぼかしを入れた画像 [SEE FILE] をクリックした後で表示される、ぼかしを入れた画像。](https://developer.android.com/static/codelabs/android-workmanager/img/f290bdf51724bfd7.png?authuser=09&hl=ja)