Android JetpackのWorkManagerの使い方

#Android JetpackのWorkManagerの使い方
JetpackはAndroidデベロッパーのために開発されたライブラリ、ツールなどのことを総称したものです。
今回はその中のWorkManagerという機能について説明します。

https://developer.android.com/jetpack?hl=ja

##WorkManagerとは
APIの一つで、アプリが終了したり再起動しても延期できるタスクをスケジューリングする機能のようです。
定期的なサーバとの同期や、ログなどの送信に便利そうです。

https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja

下記が下位互換性
API 23 以上が搭載されたデバイスでは JobScheduler を使用
API 14～22 が搭載されたデバイスでは BroadcastReceiver と AlarmManager を組み合わせて使用

JobSchdulerなどは確かによく使われていましたが、これをさらにパッケージ化したものでしょうか。


##ライブラリ追加
WorkManagerライブラリを使用するために、下記を追加してください。

```build.gradle
def work_version = "2.3.4"
implementation "androidx.work:work-runtime:$work_version"
implementation "androidx.work:work-runtime-ktx:$work_version"
```

##Worker
Workerでタスクを定義します。
doWorkメソッド内に実行したい処理を記述します。
そして、 Resultクラスでタスクの実行結果を受け取ることができます。

```MyWorkManager.kt
class MyWorkManager(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    override fun doWork(): Result {
//　　　　ここに実行したい処理を書く

        return Result.success()
    }
}
```

Resultクラスで返される状態は下記です

- Result.success(): タスクが正常に終了したかどうか
- Result.failure(): タスクが失敗したかどうか
- Result.retry(): 後でタスクを再試行する必要があるかどうか

##周期的実行
タスクの実行には一回だけの実行と、周期的に実行できるオプションがあります。
WorkerRequestを使います

- OneTimeWorkRequest: 一回だけ
- PeriodicWorkRequest: 定期的

例えば10秒ごとの周期的なタスクにする場合は下記のように作ります。

```kotlin
val periodic: Duration = Duration.ofSeconds(10L)
val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic ).build()
```


もしこんなエラーが出たら、ここをみてください。

https://qiita.com/kph7mgb/items/28ee37957976e80e38f2

```
Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper ‘-jvm-target’ option
```

その後、タスクをシステムにぶん投げます

```java
WorkManager.getInstance(this).enqueue(uploadWorkRequest)
```

##タスク実行タイミング
各タスクをデバイスの状態によって変えたい時に使えるのが、Constraintsクラスです。

- setRequiredNetworkType: ネットワーク状態
- setRequiresBatteryNotLow: 電池残量がたくさんある時
- setRequiresCharging: 電源に接続されてる時
- setRequiresDeviceIdle: デバイスがアイドル状態の時
- setRequiresStorageNotLow: ストレージ容量がたくさんある時

下記は一例です

```kotlin
var constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(true)
            .setRequiresStorageNotLow(true)
            .build();

        val periodic: Duration = Duration.ofSeconds(10L)
        val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic)
            .setConstraints(constraints)
            .build();
```

タスクを10分後に実行したい時は、下記を設定します。

```kotlin
val periodic: Duration = Duration.ofSeconds(10L)
val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic)
            .setInitialDelay(10, TimeUnit.MINUTES)
            .build()
```

##タスク再試行時間

タスクを再試行したい場合には、WorkerクラスからResult.retry()を返すようです。
また、バックオフという遅延時間を設定することで、タスクの増加時間をずらすことができるようです。

```kotlin
val periodic: Duration = Duration.ofSeconds(10L)
val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic)
            .setBackoffCriteria(
                    BackoffPolicy.LINEAR,
                    OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                    TimeUnit.MILLISECONDS)
            .build()
```
##Worker進捗・監視
Workerを進捗を設定するためにCoroutineWorkerクラスを使用します。
下記で0から１００で進捗を設定することができます。

```kotlin

class ProgressWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {

    companion object {
        const val Progress = "Progress"
        private const val delayDuration = 1L
    }

    override suspend fun doWork(): Result {
        val firstUpdate = workDataOf(Progress to 0)
        val lastUpdate = workDataOf(Progress to 100)
        setProgress(firstUpdate)
        delay(delayDuration)
        setProgress(lastUpdate)
        return Result.success()
    }
}

```

次にWorkerを監視するためには、observe機能を使用します。
observerにはLifecycleOwnerクラスを使用してタスクの状態を作成する必要があります。
getWorkInfoByIdLiveDataにはWorkerRequestのidを入れて下さい。

```kotlin

WorkManager.getInstance(applicationContext)
            .getWorkInfoByIdLiveData(uploadWorkRequest.id)
            .observe(observer, Observer { workInfo: WorkInfo? ->
                if (workInfo != null) {
                    val progress = workInfo.progress
                    val value = progress.getInt(Progress, 0)
                    // 進捗状態を使った処理
                }
            })
```

上記でタスクを実行しながら状態をユーザーにお知らせすることが可能です。

##まとめ
他にもいろいろ機能を追加することが可能ですが、基本的な使い方はここで終わりです。
これを使えば今まで非同期通信のスケジューリングなどが簡単に作成することが可能になりました。
以上です