1. 始める前に
この Codelab では、遅延可能なバックグラウンド処理用のライブラリで、下位互換性、柔軟性、シンプルさを兼ね備えた WorkManager を取り上げます。WorkManager
は、Android 上で遅延可能な処理を確実に実行するための推奨タスク スケジューラです。
前提条件
- StateFlow と ViewModel に関する知識。これらのクラスを初めて使用する場合は、Compose での ViewModel と状態 Codelab(特に ViewModel と状態関連)、または Room によるデータの読み取りと更新 Codelab(特に Flow と StateFlow 関連)をご確認ください。
- リポジトリと依存関係注入に関する知識。確認するには、リポジトリと手動 DI を追加するをご覧ください。
- アプリにコルーチンを実装する能力
学習内容
- プロジェクトに WorkManager を追加する方法
- 簡単なタスクのスケジュール設定を行う方法
- ワーカーの入出力パラメータを設定する方法
- ワーカー チェーンを作成する方法
演習内容
- スターター アプリを変更して、WorkManager を使用するようにします。
- 画像にぼかしを入れる処理リクエストを実装します。
- 処理チェーンを作成して連続した処理グループを実装します。
- スケジュール設定する処理との間でデータの受け渡しをします。
必要なもの
- Android Studio の最新の安定版
- インターネット接続
2. アプリの概要
最近のスマートフォンは、写真撮影の性能が良すぎるくらいです。神秘的な被写体を撮影すると確実にぼけるという時代は終わりました。
この Codelab では、写真にぼかしを入れて結果をファイルに保存するアプリ、Blur-O-Matic を作成します。ネッシーのような怪物か、おもちゃの潜水艦か、Blur-O-Matic を使えば、誰にもわからなくります。
この画面では、ラジオボタンで画像をどの程度ぼかすかを選択できます。[Start] ボタンをクリックすると、画像がぼかし加工されて保存されます。
現時点では、ぼかし加工はされず、最終的な画像も保存されません。
この Codelab では、アプリに WorkManager を追加し、画像のぼかし加工で作成された一時ファイルをクリーンアップするワーカー、画像をぼかし加工するワーカー、最終的な画像を保存して [See File] ボタンをクリックすると表示できるワーカーを作成します。また、バックグラウンド処理のステータスを監視し、それに応じてアプリの UI を更新する方法も学習します。
3.Blur-O-Matic スターター アプリを確認する
スターター コードを取得する
まず、スターター コードをダウンロードします。
または、GitHub リポジトリのクローンを作成してコードを入手することもできます。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout starter
Blur-o-matic アプリのコードは、こちらの GitHub リポジトリで確認できます。
スターター コードを実行する
次の手順でスターター コードを確認し、よく理解してください。
- Android Studio でスターター コードのプロジェクトを開きます。
- Android デバイスまたはエミュレータでアプリを実行します。
画面には、画像のぼかし加工の程度を選択できるラジオボタンがあります。[Start] ボタンをクリックすると、画像がぼかし加工されて保存されます。
この時点では、[Start] ボタンをクリックしても、ぼかし加工の程度は適用されません。
スターター コードのチュートリアル
ここでは、プロジェクトの構造を把握します。プロジェクトに含まれる重要なファイルとフォルダの説明を以下に示します。
WorkerUtils
: コンビニエンス メソッドです。後でNotifications
を表示し、ビットマップをファイルに保存するコードを作成するために使用します。BlurViewModel
: このビューモデルは、アプリの状態を保存し、リポジトリとやり取りします。WorkManagerBluromaticRepository
: WorkManager でバックグラウンド処理を開始するクラスです。Constants
: Codelab で使用する定数が含まれる静的クラスです。BluromaticScreen
: UI 用のコンポーズ可能な関数が含まれ、BlurViewModel
とやり取りします。これらのコンポーズ可能な関数は、画像を表示し、希望のぼかしレベルを選択するためのラジオボタンを含んでいます。
4. WorkManager とは
WorkManager は Android Jetpack の一部であり、待機的実行と確実な実行というニーズの組み合わせをもつバックグラウンド処理のためのアーキテクチャ コンポーネントです。待機的実行とは、WorkManager がバックグラウンド処理を可能になり次第実行することを指します。確実な実行とは、たとえばアプリを終了した場合など、さまざまな状況下で WorkManager がその処理の開始ロジックを保持して実行することを指します。
WorkManager は特に柔軟性に優れたライブラリで、他にも多くのメリットがあります。メリットには次のようなものがあります。
- 非同期の 1 回限りのタスクと定期的なタスクの両方をサポート
- ネットワーク状態、保存容量、充電ステータスなどの制約をサポート
- 処理の並列実行など、複雑な処理リクエストのチェーンを作成可能
- 処理リクエストの出力を、後続の処理リクエストの入力として使用可能
- API レベル 14 までの後方互換性(注を参照)
- Google Play 開発者サービスの有無を問わず動作
- システムの健全性に関するベスト プラクティスに準拠
- アプリの UI に処理リクエストの状態を簡単に表示するためのサポート
5. WorkManager の用途
WorkManager ライブラリの使用が適しているのは、完了することが求められるタスクです。これらのタスクの実行にあたっては、処理をキューに追加した後でアプリの実行を継続させる必要はありません。タスクは、アプリが閉じられても、ユーザーがホーム画面に戻っても実行されます。
WorkManager の使用が適したタスクの例を以下に示します。
- 最新ニュース記事の定期的な照会
- 画像に対するフィルタの適用と保存
- ローカルデータとネットワークとの定期的な同期
WorkManager は、メインスレッドを離れてタスクを実行する方法の一つですが、あらゆる種類のメインスレッド外のタスクに対応できるわけではありません。別の方法として、以前の Codelab で説明したコルーチンもあります。
WorkManager の用途について詳しくは、バックグラウンド処理ガイドをご覧ください。
6. アプリに WorkManager を追加する
WorkManager
には、次の Gradle 依存関係が必要です。これは以下のように、ビルドファイルにすでに含まれています。
app/build.gradle.kts
dependencies {
// WorkManager dependency
implementation("androidx.work:work-runtime-ktx:2.8.1")
}
work-runtime-ktx
は必ず最新の安定版を使用してください。
バージョンを変更した場合は、必ず [Sync Now] をクリックして、更新した Gradle ファイルとプロジェクトを同期してください。
7. WorkManager の基礎
把握しておくべき WorkManager クラスとして、以下のものがあります。
Worker
/CoroutineWorker
: Worker はバックグラウンド スレッドで同期的に処理を実行するクラスです。非同期処理を行いたい場合は、Kotlin コルーチンと相互運用性がある CoroutineWorker を使用できます。このアプリでは、CoroutineWorker クラスから拡張し、doWork()
メソッドをオーバーライドします。このメソッドに、バックグラウンドで実行する処理のコードを記述します。WorkRequest
: このクラスは処理実行のリクエストを表します。WorkRequest
では、ワーカーを 1 回実行する必要があるか定期的に実行する必要があるかを定義します。また、処理を実行する際にWorkRequest
で特定の条件を満たしている必要がある場合には Constraints を設定します。たとえば、リクエストされた作業を開始するにはデバイスが充電中でなければならない、などです。WorkRequest
の作成の一環としてCoroutineWorker
を渡します。WorkManager
: このクラスが実際にWorkRequest
をスケジュールして実行します。指定された制約を尊重しながら、負荷がシステム リソースに分散されるようWorkRequest
をスケジュールします。
今回は、画像をぼかし加工するコードを含んだ BlurWorker
クラスを新たに定義します。[Start] ボタンをクリックすると、WorkManager が WorkRequest
オブジェクトを作成してキューに追加します。
8. BlurWorker を作成する
このステップでは、res/drawable
フォルダにある android_cupcake.png
という画像に対して、いくつかの関数をバックグラウンドで実行します。これらの関数は画像にぼかしを入れます。
- Android のプロジェクト ペインでパッケージ
com.example.bluromatic.workers
を右クリックし、[New] -> [Kotlin Class/File] を選択します。 - 作成された Kotlin クラスに
BlurWorker
という名前を付けます。これを必須のコンストラクタ パラメータを使用してCoroutineWorker
から拡張します。
workers/BlurWorker.kt
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
}
BlurWorker
クラスは、Worker
クラスではなく、より具体的な CoroutineWorker
クラスを拡張しています。CoroutineWorker
クラスでの doWork()
の実装は一時停止する関数であり、Worker
では不可能な非同期コードを実行できます。WorkManager でのスレッド化に関するガイドで詳しく説明されているように「CoroutineWorker は、Kotlin ユーザーにおすすめの実装」です。
この時点では、Android Studio で class BlurWorker
の下にエラーを示す赤色の波線が表示されます。
「class BlurWorker
」というテキストにカーソルを合わせると、エラーに関する追加情報がポップアップで表示されます。
エラー メッセージは、必要とされる doWork()
メソッドのオーバーライドを行っていないことを示しています。
doWork()
メソッドの中で、表示されているカップケーキの画像にぼかしを入れるコードを記述します。
以下の手順でエラーを修正し、doWork()
メソッドを実装します。
- 「BlurWorker」というテキストをクリックして、カーソルをクラスコード内に置きます。
- Android Studio のメニューで [Code] > [Override Methods...] を選択します。
- [Override Members] ポップアップで、
doWork()
を選択します。 - [OK] をクリックします。
- クラス宣言の直前で、
TAG
という名前の変数を作成し、値BlurWorker
を代入します。この変数は、doWork()
メソッドとは特に関係ありませんが、後でLog()
の呼び出しで使用します。
workers/BlurWorker.kt
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
...
- 処理の実行タイミングを把握しやすくするには、
WorkerUtil
のmakeStatusNotification()
関数を使用する必要があります。この関数を使用することで、通知バナーを画面上部に簡単に表示できます。
doWork()
メソッド内で、makeStatusNotification()
関数を使用してステータス通知を表示し、ぼかし加工ワーカーが起動して画像にぼかしを入れていることをユーザーに通知します。
workers/BlurWorker.kt
import com.example.bluromatic.R
...
override suspend fun doWork(): Result {
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
...
return try...catch
コードブロックを追加します。ここで実際のぼかし加工の処理が行われます。
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
} catch (throwable: Throwable) {
}
...
try
ブロックにResult.success()
の呼び出しを追加します。catch
ブロックにResult.failure()
の呼び出しを追加します。
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
Result.success()
} catch (throwable: Throwable) {
Result.failure()
}
...
try
ブロックで、picture
という名前の変数を作成し、BitmapFactory.decodeResource
()
メソッドを呼び出して、返されるビットマップを代入します。呼び出しの際には、アプリのリソース パッケージと、カップケーキ画像のリソース ID を渡します。
workers/BlurWorker.kt
...
return try {
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
Result.success()
...
blurBitmap()
関数を呼び出し、picture
変数と、blurLevel
パラメータとして1
の値を渡して、ビットマップにぼかしを入れます。- その結果を
output
という名前の新しい変数に保存します。
workers/BlurWorker.kt
...
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
val output = blurBitmap(picture, 1)
Result.success()
...
- 新たに変数
outputUri
を作成し、writeBitmapToFile()
関数の呼び出し結果を代入します。 writeBitmapToFile()
の呼び出しでは、引数としてアプリのコンテキストとoutput
変数を渡します。
workers/BlurWorker.kt
...
val output = blurBitmap(picture, 1)
// Write bitmap to a temp file
val outputUri = writeBitmapToFile(applicationContext, output)
Result.success()
...
outputUri
変数を含んだ通知メッセージをユーザーに表示するコードを追加します。
workers/BlurWorker.kt
...
val outputUri = writeBitmapToFile(applicationContext, output)
makeStatusNotification(
"Output is $outputUri",
applicationContext
)
Result.success()
...
catch
ブロックで、画像のぼかし処理中にエラーが発生したことを示すエラー メッセージをログに記録します。Log.e()
の呼び出しには、前に定義したTAG
変数、適切なメッセージ、スローされた例外を渡します。
workers/BlurWorker.kt
...
} catch (throwable: Throwable) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_applying_blur),
throwable
)
Result.failure()
}
...
CoroutineWorker,
は、デフォルトでは Dispatchers.Default
として実行されますが、withContext()
を呼び出して希望のディスパッチャを渡すことで変更できます。
withContext()
ブロックを作成します。withContext()
の呼び出しの中でDispatchers.IO
を渡し、ブロックされる可能性がある IO 操作のための特別なスレッドプールでラムダ関数が実行されるようにします。- 以前に記述した
return try...catch
コードをこのブロックに移動します。
...
return withContext(Dispatchers.IO) {
return try {
// ...
} catch (throwable: Throwable) {
// ...
}
}
...
ラムダ関数内から return
を呼び出すことはできないため、Android Studio に次のエラーが表示されます。
このエラーは、ポップアップに示されているように、ラベルを追加することで解決できます。
...
//return try {
return@withContext try {
...
この Worker は非常に短時間で実行されるため、コードに遅延を追加して低速な処理をエミュレートすることをおすすめします。
withContext()
のラムダ内に、delay()
ユーティリティ関数の呼び出しを追加して定数DELAY_TIME_MILLIS
を渡します。この呼び出しは、この Codelab 用に遅延を通知メッセージ間に入れるためのものです。
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay
...
return withContext(Dispatchers.IO) {
// This is an utility function added to emulate slower work.
delay(DELAY_TIME_MILLIS)
val picture = BitmapFactory.decodeResource(
...
9. WorkManagerBluromaticRepository を更新する
リポジトリは、WorkManager とのすべてのやり取りを扱います。この構造は、関心の分離という設計原則を守るもので、Android アーキテクチャ パターンとして推奨されています。
data/WorkManagerBluromaticRepository.kt
ファイルのWorkManagerBluromaticRepository
クラス内で、workManager
という名前のプライベート変数を作成し、そこにWorkManager.getInstance(context)
を呼び出して得たWorkManager
インスタンスを格納します。
data/WorkManagerBluromaticRepository.kt
import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
// New code
private val workManager = WorkManager.getInstance(context)
...
WorkManager で WorkRequest を作成してキューに追加する
それでは、WorkRequest
を作成して WorkManager に実行させましょう。WorkRequest
には次の 2 種類があります。
OneTimeWorkRequest
: 1 回だけ実行されるWorkRequest
PeriodicWorkRequest
: 定期的に繰り返し実行されるWorkRequest
[Start] ボタンが選択されたときに画像にぼかしを入れるのは 1 回だけです。
この処理は、[Start] ボタンをクリックしたときに呼び出す applyBlur()
メソッドで行います。
以下のステップは applyBlur()
メソッド内で完了します。
- ぼかし処理ワーカー用の
OneTimeWorkRequest
を作成し、WorkManager KTX からOneTimeWorkRequestBuilder
拡張関数を呼び出して、blurBuilder
という名前の新しい変数に代入します。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
workManager
オブジェクトのenqueue()
メソッドを呼び出して処理を開始します。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// Start the work
workManager.enqueue(blurBuilder.build())
}
- アプリを実行し、[Start] ボタンをクリックすると通知が表示されます。
この時点では、どの選択肢を選んでも画像に適用されるぼかしの程度は同じです。以降のステップでは、選んだ選択肢に応じてぼかしの程度が変化します。
画像が正常にぼかし加工されたことを確認するには、Android Studio で [Device Explorer] を開きます。
次に、[data] > [data] > [com.example.bluromatic] > [files] > [blur_filter_outputs] > <URI> に移動して、実際にカップケーキの画像にぼかしが入っていることを確認します。
10. 入力データと出力データ
リソース ディレクトリ内の画像アセットにぼかしを入れることはできましたが、Blur-O-Matic が革新的な画像編集アプリになるよう、画面に表示されている画像にぼかしを入れて結果を表示できるようにする必要があります。
そのために、WorkRequest
への入力として表示されるカップケーキ画像の URI を指定し、WorkRequest
の出力を使用して、ぼかしを入れた最終的な画像を表示します。
入力と出力は、Data
オブジェクトを介してワーカーの間で受け渡しされます。Data
オブジェクトは、Key-Value ペアの軽量コンテナです。ワーカーと WorkRequest
との間でやり取りされる少量のデータを格納することを目的としています。
次のステップでは、入力データ オブジェクトを作成して URI を BlurWorker
に渡します。
入力データ オブジェクトを作成する
data/WorkManagerBluromaticRepository.kt
ファイルのWorkManagerBluromaticRepository
クラス内で、imageUri
というプライベート変数を作成します。- コンテキスト メソッド
getImageUri()
を呼び出して、この変数に画像 URI を代入します。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
private var imageUri: Uri = context.getImageUri() // <- Add this
private val workManager = WorkManager.getInstance(context)
...
アプリコードには、入力データ オブジェクトを作成するための createInputDataForWorkRequest()
ヘルパー関数が用意されています。
data/WorkManagerBluromaticRepository.kt
// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
val builder = Data.Builder()
builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
return builder.build()
}
まず、このヘルパー関数が Data.Builder
オブジェクトを作成します。そこに imageUri
と blurLevel
が Key-Value ペアとして設定されます。その後、return builder.build()
を呼び出したときに Data オブジェクトが作成されて返されます。
- WorkRequest の入力データ オブジェクトを設定するために、
blurBuilder.setInputData()
メソッドを呼び出します。引数でcreateInputDataForWorkRequest()
ヘルパー関数を呼び出すと、1 ステップでデータ オブジェクトを作成して渡すことができます。createInputDataForWorkRequest()
関数の呼び出しでは、blurLevel
変数とimageUri
変数を渡します。
data/WorkManagerBluromaticRepository.kt
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// New code for input data object
blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))
workManager.enqueue(blurBuilder.build())
}
入力データ オブジェクトにアクセスする
次に、BlurWorker
クラスの doWork()
メソッドを更新して、入力データ オブジェクトを使って渡された URI とぼかしレベルを取得しましょう。blurLevel
に値が指定されていない場合は、デフォルトで 1
が使用されます。
doWork()
メソッド内で次の処理を行います。
resourceUri
という名前の変数を作成し、inputData.getString()
を呼び出し、入力データ オブジェクトの作成時にキーとして使用されていた定数KEY_IMAGE_URI
を渡して、返される値をこの変数に代入します。
val resourceUri = inputData.getString(KEY_IMAGE_URI)
blurLevel
という名前の変数を作成します。inputData.getInt()
を呼び出し、入力データ オブジェクトの作成時にキーとして使用されていた定数BLUR_LEVEL
を渡して、返される値をこの変数に代入します。この Key-Value ペアが作成されていない場合は、デフォルト値の1
を指定します。
workers/BlurWorker.kt
import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {
// ADD THESE LINES
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)
// ... rest of doWork()
}
URI を使用して、画面に表示されるカップケーキの画像にぼかしを入れましょう。
resourceUri
変数にデータが設定されていることを確認します。値が設定されていない場合は例外がスローされます。以下のコードでは、最初の引数の評価結果が false であった場合にIllegalArgumentException
をスローするrequire()
ステートメントを使用しています。
workers/BlurWorker.kt
return@withContext try {
// NEW code
require(!resourceUri.isNullOrBlank()) {
val errorMessage =
applicationContext.resources.getString(R.string.invalid_input_uri)
Log.e(TAG, errorMessage)
errorMessage
}
画像のソースが URI として渡されるので、URI が指すコンテンツを読み取る ContentResolver オブジェクトが必要です。
contentResolver
オブジェクトをapplicationContext
値に追加します。
workers/BlurWorker.kt
...
require(!resourceUri.isNullOrBlank()) {
// ...
}
val resolver = applicationContext.contentResolver
...
- 画像のソースが URI で渡されるようになったので、
BitmapFactory.decodeResource()
ではなくBitmapFactory.decodeStream()
を使用してビットマップ オブジェクトを作成します。
workers/BlurWorker.kt
import android.net.Uri
...
// val picture = BitmapFactory.decodeResource(
// applicationContext.resources,
// R.drawable.android_cupcake
// )
val resolver = applicationContext.contentResolver
val picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
blurBitmap()
関数の呼び出しでblurLevel
変数を渡します。
workers/BlurWorker.kt
//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)
出力データ オブジェクトを作成する
この Worker に関する作業が完了し、Result.success()
で出力 URI を出力データ オブジェクトとして返すことができるようになりました。出力 URI を出力データ オブジェクトとして指定することで、他のワーカーによる操作が容易になっています。この手法は、次のセクションでワーカー チェーンを作成する際に便利です。
その方法は次のとおりです。
Result.success()
のコードの前で、outputData
という名前の変数を作成します。workDataOf()
関数を呼び出し、キーには定数KEY_IMAGE_URI
を、値には変数outputUri
を使用して、この変数に値を設定します。workDataOf()
関数は、渡された Key-Value ペアから Data オブジェクトを作成します。
workers/BlurWorker.kt
import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
Result.success()
のコードを更新して、この新しい Data オブジェクトを引数とします。
workers/BlurWorker.kt
//Result.success()
Result.success(outputData)
- 出力データ オブジェクトが URI を使用するようになり、通知を表示するコードは不要となったため削除します。
workers/BlurWorker.kt
// REMOVE the following notification code
//makeStatusNotification(
// "Output is $outputUri",
// applicationContext
//)
アプリを実行する
この時点でアプリを実行すると、コンパイルは成功するはずです。ぼかしの入った画像が Device Explorer から確認できますが、画面にはまだ表示されません。
なお、画像を表示するには、[Synchronize] が必要な場合があります。
成功です。WorkManager
を使用して入力画像にぼかしを入れることができました。
11. 処理のチェーンを作成する
ここで行っているのは、画像にぼかしを入れるという課題だけです。最初のステップには最適ですが、アプリにはまだ重要な機能が欠けています。
- 一時ファイルがクリーンアップされません。
- 画像が永続ファイルに保存されません。
- 写真に常に同程度のぼかししか入れられません。
WorkManager の処理チェーンを使用すると、上記の機能を追加できます。WorkManager を使用すると、個別に作成した WorkerRequest
を順次または並列に実行できます。
このセクションでは、下図のような処理チェーンを作成します。
各箱は WorkRequest
を表します。
チェーン化のもう 1 つの特長は、入力を受けて出力を生成する機能です。WorkRequest
の出力がチェーン内で後続する WorkRequest
の入力になります。
画像にぼかしを入れる CoroutineWorker
はすでにありますが、一時ファイルをクリーンアップする CoroutineWorker
と、画像を永続的に保存する CoroutineWorker
も必要です。
CleanupWorker を作成する
CleanupWorker
は、一時ファイルが存在する場合にそれを削除します。
- Android のプロジェクト ペインでパッケージ
com.example.bluromatic.workers
を右クリックし、[New] -> [Kotlin Class/File] を選択します。 - 作成された Kotlin クラスに
CleanupWorker
という名前を付けます。 - 次のコード例のように、CleanupWorker.kt のコードをコピーします。
ファイル操作はこの Codelab の範囲外ですので、CleanupWorker
に以下のコードをコピーして構いません。
workers/CleanupWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
/**
* Cleans up temporary files generated during blurring process
*/
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend 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(
applicationContext.resources.getString(R.string.cleaning_up_files),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
return@withContext 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) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_cleaning_file),
exception
)
Result.failure()
}
}
}
}
SaveImageToFileWorker を作成する
SaveImageToFileWorker
クラスは、一時ファイルを永続ファイルに保存します。
SaveImageToFileWorker
は、入力と出力を取ります。入力は、ぼかしを入れた一時画像の URI の String
であり、キー KEY_IMAGE_URI
を使用して格納されます。出力は、ぼかしを入れて保存された画像の URI の String
であり、キー KEY_IMAGE_URI
で保存されます。
- Android のプロジェクト ペインでパッケージ
com.example.bluromatic.workers
を右クリックし、[New] -> [Kotlin Class/File] を選択します。 - 作成された Kotlin クラスに
SaveImageToFileWorker
という名前を付けます。 - 次のサンプルコードのように、SaveImageToFileWorker.kt のコードをコピーします。
ファイル操作はこの Codelab の範囲外ですので、SaveImageToFileWorker
に以下のコードをコピーして構いません。用意されているコードで、resourceUri
と output
の値をキー KEY_IMAGE_URI
で取得、保存する方法を確認してください。この処理は、以前に入力データ オブジェクトと出力データ オブジェクト用に作成したコードとよく似ています。
workers/SaveImageToFileWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
/**
* Saves the image to a permanent file
*/
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
private val title = "Blurred Image"
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z",
Locale.getDefault()
)
override suspend 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(
applicationContext.resources.getString(R.string.saving_image),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
val resolver = applicationContext.contentResolver
return@withContext 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,
applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
)
Result.failure()
}
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_saving_image),
exception
)
Result.failure()
}
}
}
}
処理チェーンを作成する
今のところ、このコードは WorkRequest
を 1 つだけ作成して実行します。
このステップでは、1 つのぼかし加工リクエストではなく WorkRequest のチェーンを作成して実行するようにコードを変更します。
WorkRequest のチェーンで最初に行う処理リクエストは、一時ファイルのクリーンアップです。
OneTimeWorkRequestBuilder
の代わりにworkManager.beginWith()
を呼び出します。
beginWith()
メソッドを呼び出すと、WorkContinuation
オブジェクトが返され、チェーンの最初の処理リクエストを使って WorkRequest
チェーンの始点が作成されます。
data/WorkManagerBluromaticRepository.kt
import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
override 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 blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...
この処理リクエストのチェーンに追加するには、then()
メソッドを呼び出し、WorkRequest
オブジェクトを渡します。
- 1 つの WorkRequest のみをキューに追加していた
workManager.enqueue(blurBuilder.build())
の呼び出しを削除します。 .then()
メソッドを呼び出して、次の処理リクエストをチェーンに追加します。
data/WorkManagerBluromaticRepository.kt
...
//workManager.enqueue(blurBuilder.build())
// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
- 画像を保存してチェーンに追加する処理リクエストを作成します。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.SaveImageToFileWorker
...
continuation = continuation.then(blurBuilder.build())
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.build()
continuation = continuation.then(save)
...
- この処理を開始するには、継続オブジェクトの
enqueue()
メソッドを呼び出します。
data/WorkManagerBluromaticRepository.kt
...
continuation = continuation.then(save)
// Start the work
continuation.enqueue()
...
このコードは、CleanupWorker
WorkRequest
の次に BlurWorker
WorkRequest
、その次に SaveImageToFileWorker
WorkRequest
という WorkRequest のチェーンを生成して実行します。
- アプリを実行します。
[Start] がクリックできるようになり、各ワーカーが実行されたときに通知が表示されるようになりました。ぼかしを入れた画像は引き続き Device Explorer で確認できますが、今後のセクションでは、ぼかしを入れた画像をデバイスで確認できるように別のボタンを追加します。
以下のスクリーンショットでは、現在実行されている Worker を示す通知メッセージが表示されています。
出力フォルダには、ぼかしが入った複数の画像(ぼかし加工の中間段階の画像と、選択したぼかし量で表示される最終画像)が残っています。
お疲れさまでした。これで、一時ファイルのクリーンアップ、画像のぼかし加工、保存ができるようになりました。
12. 解答コードを取得する
この Codelab の完成したコードをダウンロードするには、以下のコマンドを使用します。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout intermediate
または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。
この Codelab の解答コードを確認する場合は、GitHub で表示します。
13. おわりに
おめでとうございます。Blur-O-Matic アプリが完成しました。このプロセスでは以下について学びました。
- プロジェクトへの WorkManager の追加
OneTimeWorkRequest
のスケジュール設定- 入出力パラメータ
- 処理チェーンによる
WorkRequest
の連結
WorkManager は、この Codelab で取り上げたもの以外にも、繰り返し処理、テスト支援ライブラリ、並列処理リクエスト、入力マージツールなど、多くの機能をサポートしています。
詳しくは、WorkManager でタスクのスケジュールを設定するをご覧ください。