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 に進んでください。