1. WorkManagerとは?
紹介する内容は**Andorid Jetpackというライブラリーのバックグラウンドを担当するWorkManager**です。
WorkManagerを初お題にした理由はちょうど苦戦中で記録として残しておきたいからです。WorkManaagerについてはGoogleが提供しているサンプルとCodeLabを参考にしてください。
作成中ではWorkManagerのバージョンは1.0.0-alpha05
で、まだアルファバーションなのでAPIや内部動作は今後変わる可能性があるかもしれません。
WorkManagerは既存にBackground作業のために使っていたライブラリーを端末のAPIレベルや環境に合わせ、単純化されたAPIとして提供するライブラリーです。
WorkManagerは内部的にユーザーのAPIレベル及び端末がGoogle Play Servicesにアクセスできるかなどの条件によってJobScheduler、FirebaseJobDispatcher、AlarmManagerなどのライブラリーを適切に選択して使用します。
JobSchedulerはAPI21に追加され、FirebaseJobDispatcherはAPI9から利用できますが、Google Play Servicesが端末にインストールされているなどの制約があり、Androidでバージョンアップされればエンジニアよりユーザーの便宜に合わせてアップデートするため開発の難易度が上がります。
2. WorkManagerの設定
implementation "android.arch.work:work-runtime:1.0.0-alpha05
3. 基本的な構成
簡単に代表的なクラスについて説明します。
##Worker
ロジック作成でWorkerを使用するためにはWorkerの継承が必要です。Worker class
にはdoWork()
というabstractメソッドが存在し継承したクラスのdoWork()
メソッド内でロジックを実装します。
class SampleWorker : Worker() {
override fun doWork(): Result {
// do something
return Result.SUCCESS
}
}
##WorkRequest
Workerを実行するためにWorkRequestはWorkerに何らかのリクエストを要請します。WorkRequestもabstract class
でサブクラスとしてOneTimeWorkRequest
、PeriodicWorkRequest
のいずれかを利用します。
##WorkManager
このクラスはWorkRequestをスケージュールして実行するクラスです。指定された制約条件を順守しシステムリソースの負荷分散するようにスケージューリングします。
##WorkStatus
作業の進行状態を表します。この情報でUIを更新したりまたスケージュールが重複しないよう、実行中かどうか照会するときにつかいます。WorkStatusは識別できるID情報(UUID)とTag情報があり、ステータスを現すState enum変数があります。StateはENQUEUED
、RUNNNIG
、SUCCEEDED
、FAILED
、BLOCKED
、CANCELLED
があり、SUCCEEDED
or FAILED
or CANCELLED
の場合、作業が完了したと判断します。
##Constraints
制約条件を設定するクラスです。設定できる条件は下記の通りです。
- 端末が充電中の時
- 端末のバッテリーが不足してない時
- 端末がIdle状態の時
- 端末の保存空間が不足してない時
4. 実装例
とても簡単なコードです
WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder(SampleWorker::class.java).build())
バックグラウンド実装がコード一行で書けます。もちろん実際には上記のようには書きませんが、とにかく上記のコードは上手く動きます。WorkRequestに2つのサブクラスがありますが、OneTimeWorkRequest
とPeriodicWorkRequest
の中で適切に使う必要があります。
##OnetimeWorkRequest
作業を繰り返さなくて良い場合はOneTimeWorkRequest
を使います。例えば画像アップロードやダウンロードなどがあります。PeriodicWorkRequest
はWorkManagerのenqueue
メソッドしか利用できないですが、OneTimeWorkRequest
は他にもっと選択肢があります。
各WorkRequestを特定の順番通りに実行することができ、たとえば下記のようなユースケースなどがあります。
- ローカルDBに大量のデータをInsertする作業
- ローカルDBに保存したデータを区間別に分離し、Updateする作業
- 2番まで完了すると処理する作業
もちろん1、2、3の作業を一つのWoker
で処理することもできます。それぞれのWoker
で定義して作業を分離し、その作業が順次的に実行することもあると思います。その時OneTimeWorkRequest
を使ってSequentialに進めるように設定すれば良いです。
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
上記のコードは先にworkA
を実行しworkA
の作用が完了するとworkB
を、workB
の作業が完了するとworkC
を実行します。後、beginWith
は調べてみると複数の引数を渡すことができます。
WorkManager.getInstance()
.beginWith(workA1, workA2, workA3)
.then(workB)
.then(workC)
.enqueue()
beginWith
の引数のworkA1
、workA2
、workA3
は並列で実行します。並列で動くのでwork1
、work2
、work3
の作業が完了した時点でバラバラでもworkB
はwork1
、work2
、work3
の作業が全て完了した後に実行されます。
他にもcombine
を活用するともっと複雑な順序が設定できます。
コードで上記のSequenceを設定するとしたら次のように書けば良いです。
val chain1 = WorkManager.getInstance()
.beginWith(workA)
.then(workB)
val chain2 = WorkManager.getInstance()
.beginWith(workC)
.then(workD)
val chain3 = WorkContinuation
.combine(chain1, chain2)
.then(workE)
chain3.enqueue()
先にworkA
が完了後、workB
が、その次にworkC
が、その後workD
が実行されます。最終的にworkB
とworkD
完了したらworkE
が実行する構造です。
上記の機能以外にもUniqueなWorkの設定ができます。
WorkManager.getInstance().beginUniqueWork(
"unique_work_id",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequest.Builder(UniqeWork::class.java)
.build()
WorkManager
のbeginUniqueWork
のメソッドを利用すれば、1つ目の引数はWorkの名前で、2つ目の引数は既存のWorkが存在する場合、その動作に対する処理方法です。WorkManager
は一つのUniqueな名前でWorkを実行します。もし同一の名前のWorkが存在する場合はExistingWorkPolicy
の値によって内容が違います。
- REPLACE : 同じ名前のWorkがある場合、キャンセルし削除後新しいWorkを実行する
- KEEP : 同じ名前がある場合、Workが何もしない
##PeriodicWorkRequest
OneTimeWorkRequest
と違ってPeriodicWorkRequest
は名前の通り、繰り返し作業をするためのクラスです。例えばバックグラウンドでデータを定期的に同期化する作業が必要ならPeriodicWorkRequest
を使ったほうが適切だと思います。
PeriodicWorkRequest
を生成する時repeat interval
とflex interval
が設定できます。下記の説明をご覧になったほうが分かりやすいかと思います。
[ before flex | flex ][ before flex | flex ]...
[ cannot run work | can run work ][ cannot run work | can run work ]...
\____________________________________/\____________________________________/...
interval 1 interval 2 ...(repeat)
repeat interval
はWorkの繰り返し操作を設定し、デフォルト値はMIN_PERIODIC_INTERVAL_MILLIS(15分)
でそれより短い時間は許容しません。
flex interval
はWorkが実行する最小駆動時間です。デフォルト値はMIN_PERIODIC_FLEX_MILLIS(5分)
でそれより短い時間は許容しません。
下記は1時間間隔で10分以内にリクエストする簡単なコードです。
val request = PeriodicWorkRequest.Builder(SamplePeriodicWorker::class.java,
1, TimeUnit.HOURS,
10, TimeUnit.MINUTES)
.build()
WorkManager.getInstance().enqueue(request)
Workが重複しないよう、PeriodicWorkRequest
を実行する前にWorkManager
のgetStatuseByTag
あるいはgetStatusById
メソッドを呼び出し、すでに登録されているWorkを確認するかcancelAllWorkByTag
あるいはcancelWorkById
で同じWorkをキャンセルさせることが出来ます。