Andorid

WorkManager


1. WorkManagerとは?

紹介する内容はAndorid Jetpackというライブラリーのバックグラウンドを担当するWorkManagerです。

WorkManagerを初お題にした理由はちょうど苦戦中で記録として残しておきたいからです。WorkManaagerについてはGoogleが提供しているサンプルCodeLabを参考にしてください。

作成中ではWorkManagerのバージョンは1.0.0-alpha05で、まだアルファバーションなのでAPIや内部動作は今後変わる可能性があるかもしれません。

WorkManagerは既存にBackground作業のために使っていたライブラリーを端末のAPIレベルや環境に合わせ、単純化されたAPIとして提供するライブラリーです。

WorkManagerは内部的にユーザーのAPIレベル及び端末がGoogle Play Servicesにアクセスできるかなどの条件によってJobSchedulerFirebaseJobDispatcherAlarmManagerなどのライブラリーを適切に選択して使用します。

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でサブクラスとしてOneTimeWorkRequestPeriodicWorkRequestのいずれかを利用します。


WorkManager

このクラスはWorkRequestをスケージュールして実行するクラスです。指定された制約条件を順守しシステムリソースの負荷分散するようにスケージューリングします。


WorkStatus

作業の進行状態を表します。この情報でUIを更新したりまたスケージュールが重複しないよう、実行中かどうか照会するときにつかいます。WorkStatusは識別できるID情報(UUID)とTag情報があり、ステータスを現すState enum変数があります。StateはENQUEUEDRUNNNIGSUCCEEDEDFAILEDBLOCKEDCANCELLEDがあり、SUCCEEDED or FAILED or CANCELLEDの場合、作業が完了したと判断します。


Constraints

制約条件を設定するクラスです。設定できる条件は下記の通りです。

- 端末が充電中の時

- 端末のバッテリーが不足してない時

- 端末がIdle状態の時

- 端末の保存空間が不足してない時


4. 実装例

とても簡単なコードです

WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder(SampleWorker::class.java).build())

バックグラウンド実装がコード一行で書けます。もちろん実際には上記のようには書きませんが、とにかく上記のコードは上手く動きます。WorkRequestに2つのサブクラスがありますが、OneTimeWorkRequestPeriodicWorkRequestの中で適切に使う必要があります。


OnetimeWorkRequest

作業を繰り返さなくて良い場合はOneTimeWorkRequestを使います。例えば画像アップロードやダウンロードなどがあります。PeriodicWorkRequestはWorkManagerのenqueueメソッドしか利用できないですが、OneTimeWorkRequestは他にもっと選択肢があります。

各WorkRequestを特定の順番通りに実行することができ、たとえば下記のようなユースケースなどがあります。


  1. ローカルDBに大量のデータをInsertする作業

  2. ローカルDBに保存したデータを区間別に分離し、Updateする作業

  3. 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の引数のworkA1workA2workA3は並列で実行します。並列で動くのでwork1work2work3の作業が完了した時点でバラバラでもworkBwork1work2work3の作業が全て完了した後に実行されます。

他にもcombineを活用するともっと複雑な順序が設定できます。

combine.png

コードで上記の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が実行されます。最終的にworkBworkD完了したらworkEが実行する構造です。

上記の機能以外にもUniqueなWorkの設定ができます。

WorkManager.getInstance().beginUniqueWork(

"unique_work_id",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequest.Builder(UniqeWork::class.java)
.build()

WorkManagerbeginUniqueWorkのメソッドを利用すれば、1つ目の引数はWorkの名前で、2つ目の引数は既存のWorkが存在する場合、その動作に対する処理方法です。WorkManagerは一つのUniqueな名前でWorkを実行します。もし同一の名前のWorkが存在する場合はExistingWorkPolicyの値によって内容が違います。


  • REPLACE : 同じ名前のWorkがある場合、キャンセルし削除後新しいWorkを実行する

  • KEEP : 同じ名前がある場合、Workが何もしない


PeriodicWorkRequest

OneTimeWorkRequestと違ってPeriodicWorkRequestは名前の通り、繰り返し作業をするためのクラスです。例えばバックグラウンドでデータを定期的に同期化する作業が必要ならPeriodicWorkRequestを使ったほうが適切だと思います。

PeriodicWorkRequestを生成する時repeat intervalflex 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を実行する前にWorkManagergetStatuseByTagあるいはgetStatusByIdメソッドを呼び出し、すでに登録されているWorkを確認するかcancelAllWorkByTagあるいはcancelWorkByIdで同じWorkをキャンセルさせることが出来ます。