新型コロナの影響で自宅待機になってしまい、その間勉強するものとしてSunflowerリポジトリを
勧めてもらいました。
JetPackのライブラリのうち、今回はWorkManager編です
尚、引用しているソースは明記しているところ以外は、基本的には全てSunflowerのリポジトリのものです。
環境
- 確認時は
Android Studioのバージョンは3.6.2を使用しました -
JetPackはAndroidXライブラリを利用するのでCompile SDKを28以上にする必要があります
そもそもWorkManagerってなに?
WorkManager API を使用すると、アプリが終了したりデバイスが再起動したりしても
実行することが要求される延期可能な非同期タスクのスケジュールを簡単に設定できます。
です!
WorkManagerの主な機能
機能
- API 14 までの下位互換性
- API 23 以上が搭載されたデバイスでは
JobSchedulerを使用 - API 14~22 が搭載されたデバイスでは
BroadcastReceiverとAlarmManagerを組み合わせて使用
- API 23 以上が搭載されたデバイスでは
- ネットワークの可用性や充電ステータスなどの処理の制約を追加する
- 非同期の 1 回限りのタスクや定期的なタスクのスケジュールを設定する
- スケジュール設定されたタスクの監視と管理を行う
- タスクを連携させる
- アプリやデバイスが再起動してもタスクを確実に実行する
- Doze モードなどの省電力機能に準拠する
(公式より)
この中だと、APIレベルが低いものでも同じように使えるというところと、Dozeモードに対応しているのがいいですね
(Dozeモード中にバックグラウンド処理が色々動かない、というのはAndroidあるあるだと思いますので。。。)
注意点
公式によると
WorkManager は、アプリが終了したりデバイスが再起動したりしても確実に実行する必要がある遅延可能なタスク(つまり、直ちに実行する必要がないタスク)を対象としています。次に例を示します。
- ログやアナリティクスをバックエンド サービスに送信する
- アプリデータをサーバーと定期的に同期する
つまり、確実に定周期で何かをする、などということには対応していない、ということですね。
利用する際に必要なこと
必要なことは下記の4点です
- 依存関係の記載
- バックグラウンドタスクの作成
- タスクを実行する方法とタイミングを設定とタスクをシステムに引き渡す処理
依存関係の記載
build.gradleに記載する内容
build.gradleには下記の依存関係の記載を行います。
(公式ドキュメントより)
dependencies {
def work_version = "2.3.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// optional - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
}
- 必須設定
Java-
Kotlin + Coroutines-
Javaで使う場合は、Javaの設定が、Kotlinで使う場合はKotlinの設定が必須となります。
-
- オプション設定
RxJava2GCMNetworkManager-
Test helpers- これらはオプションなので、利用する場合は記載します。
Sunflowerリポジトリの場合
Sunflowerリポジトリの場合はどのようになっているかを見てみましょう
buildscript {
// Define versions in a single place
ext {
:
// App dependencies
:
workVersion = '2.1.0'
}
:
}
dependencies {
:
implementation "androidx.work:work-runtime-ktx:$rootProject.workVersion"
:
// Testing dependencies
:
androidTestImplementation "androidx.work:work-testing:$rootProject.workVersion"
:
}
Sunflowerリポジトリの場合は、下記設定となっていました。
-
Kotlinで利用しています - オプションは
Test helpersを利用しています
バックグラウンド タスクを作成する
それではSunflowerのバックグラウンドタスクを見てみましょう。
class SeedDatabaseWorker(
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = coroutineScope {
.
.
.
- タスクは通常は
Workerクラスを使用して定義します -
Workmanagerから提供されるバックグラウンドスレッドでdowork()メソッドが同期的に実行されます - ただし、
SunflowerリポジトリではCoroutineWorkerを利用しています。- こちらは
Kotlinを使う際にオススメです - その場合
doWork()メソッドはsuspend関数になり、サブスレッドで実行されます。- 実行するスレッドは、
Workerクラスとは異なり、Configurationで指定されたExecutorではありません。 -
Dispatchers.Defaultで実行されます。
- 実行するスレッドは、
- こちらは
try {
applicationContext.assets.open(PLANT_DATA_FILENAME).use { inputStream ->
JsonReader(inputStream.reader()).use { jsonReader ->
val plantType = object : TypeToken<List<Plant>>() {}.type
val plantList: List<Plant> = Gson().fromJson(jsonReader, plantType)
val database = AppDatabase.getInstance(applicationContext)
database.plantDao().insertAll(plantList)
Result.success()
}
}
} catch (ex: Exception) {
Log.e(TAG, "Error seeding database", ex)
Result.failure()
}
}
companion object {
private val TAG = SeedDatabaseWorker::class.java.simpleName
}
}
- 以降には、バックグラウンドタスクで実施する処理が記載されています。
- ここでは
assets内のJsonファイルを読み込んだあと、パースしてデータベースに保存しています。
- ここでは
-
doWork()メソッドはResultの各メソッドを返します。-
Result.success(): タスクが正常に終了したかどうか -
Result.failure(): タスクが失敗したかどうか -
Result.retry(): 後でタスクを再試行する必要があるかどうか- ここでは、データベースに保存ができれば正常終了、例外が発生した場合は失敗となっています。
-
タスク実行方法とタイミングを設定およびタスクをシステムに引き渡す処理
概要
-
Worker(CoroutineWorker)が作業単位を定義しますが、それに対し、WorkRequestは作業を実行する方法とタイミングを定義します。 - タスクには1回実行するものと、定期的に実行されるものがあります
- 1回のもの →
OneTimeWorkRequest - 定期的なもの →
PeriodicWorkRequest
- 1回のもの →
-
WorkRequestを定義した後、WorkManagerでenqueue()メソッドを使用してスケジュールを設定できます。
Sunflowerリポジトリ
// Create and pre-populate the database. See this article for more details:
// https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
WorkManager.getInstance(context).enqueue(request)
}
})
.build()
}
Sunflowerリポジトリでは下記の処理になっています。
- データベース作成時に一度だけ実施する処理
OneTimeWorkRequestBuilderでSeedDatabaseWorkerを実施している。
まとめ
- バックグラウンドタスクの作成は
Workerクラスを作成する。Kotlinの場合はCoroutineWorkerがおすすめ -
Workerクラスのdowork()メソッド内に実行する処理を書く。 - タスクの実行の際、1回しか実施しないことは、
OneTimeWorkRequest、定期的に実施するものはPeriodicWorkRequestで実行する。
以上です!
参考サイト
- Sunflowerリポジトリ
- WorkManager: 公式ドキュメント