WorkManager

# 1. WorkManagerとは?
紹介する内容は**[Andorid Jetpack](https://developer.android.com/jetpack/)**というライブラリーのバックグラウンドを担当する**WorkManager**です。

**WorkManager**を初お題にした理由はちょうど苦戦中で記録として残しておきたいからです。**WorkManaager**についてはGoogleが提供している[サンプル](https://github.com/googlecodelabs/android-workmanager)と[CodeLab]([サンプル](https://github.com/googlecodelabs/android-workmanager))を参考にしてください。

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

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

WorkManagerは内部的にユーザーのAPIレベル及び端末がGoogle Play Servicesにアクセスできるかなどの条件によって[JobScheduler](https://developer.android.com/reference/android/app/job/JobScheduler)、[FirebaseJobDispatcher](https://github.com/firebase/firebase-jobdispatcher-android)、[AlarmManager](https://developer.android.com/reference/android/app/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()`メソッド内でロジックを実装します。

```kotlin
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. 実装例
とても簡単なコードです


```kotlin
WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder(SampleWorker::class.java).build())
```
バックグラウンド実装がコード一行で書けます。もちろん実際には上記のようには書きませんが、とにかく上記のコードは上手く動きます。WorkRequestに２つのサブクラスがありますが、`OneTimeWorkRequest`と`PeriodicWorkRequest`の中で適切に使う必要があります。

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

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

1. ローカルDBに大量のデータをInsertする作業
2. ローカルDBに保存したデータを区間別に分離し、Updateする作業
3. 2番まで完了すると処理する作業

もちろん1、2、3の作業を一つの`Woker`で処理することもできます。それぞれの`Woker`で定義して作業を分離し、その作業が順次的に実行することもあると思います。その時`OneTimeWorkRequest`を使ってSequentialに進めるように設定すれば良いです。

```kotlin
WorkManager.getInstance()
  .beginWith(workA)
  .then(workB)
  .then(workC)
  .enqueue()
```

上記のコードは先に`workA`を実行し`workA`の作用が完了すると`workB`を、`workB`の作業が完了すると`workC`を実行します。後、`beginWith`は調べてみると複数の引数を渡すことができます。

```kotlin
WorkManager.getInstance()
  .beginWith(workA1, workA2, workA3)
  .then(workB)
  .then(workC)
  .enqueue()
```

`beginWith`の引数の`workA1`、`workA2`、`workA3`は並列で実行します。並列で動くので`work1`、`work2`、`work3`の作業が完了した時点でバラバラでも`workB`は`work1`、`work2`、`work3`の作業が全て完了した後に実行されます。

他にも`combine`を活用するともっと複雑な順序が設定できます。
![combine.png](https://qiita-image-store.s3.amazonaws.com/0/269837/3693d37b-b275-7bf1-3edb-b915fa767c32.png)
コードで上記のSequenceを設定するとしたら次のように書けば良いです。

```kotlin
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の設定ができます。

```kotlin
WorkManager.getInstance().beginUniqueWork(
  "unique_work_id",
  ExistingWorkPolicy.KEEP,
  OneTimeWorkRequest.Builder(UniqeWork::class.java)
  .build()
```

`WorkManager`の`beginUniqueWork`のメソッドを利用すれば、１つ目の引数はWorkの名前で、２つ目の引数は既存の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分以内にリクエストする簡単なコードです。

```kotlin
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をキャンセルさせることが出来ます。