どくぴーの備忘録

真面目なことを書こうとするクソメガネのブログ。いつ投げ捨てられるのかは不明

お酒を飲みながらWorkManagerのCodelabsをしてお勉強をした

なにこれ

本記事は 飲酒プログラミング Advent Calendar 2018 - Adventar の22日目の記事になります。

え?投稿日がそれより遅いって?知らないなぁ…。初日がまだ未投稿らしいのでそれより早ければセーフという謎理論で挑みたいと思います。

どうやらdescriptionを見ている限り、お酒を飲みながら何かしらのプログラミングに関するアクションを起こせばセーフということで、前々から理解しないとなぁと思っていたWorkManager周りのお勉強をCodelabsベースで概観だけでも把握すべくお酒を飲んでダラダラと実行しました。

書き始める前に

やっぱりお酒を飲むということで今回の飲酒の証拠を残しておきます。

ヤッホーブルーイングのTOKYO BLACKです。なんでまともに正面から撮らないんだこいつは。
ちなみにこれはCodelabs実践中の飲酒で、記事執筆のためにスーパードライと余っていた久保田 千寿も摂取するなどしております。昨日は飲んでコード書いていたら終了しました。

WorkManagerとは?

Android Jetpackのひとつであり、アップロード処理やフォアグラウンドで実行してしまうとUIを長時間ロックしてしまいそうな適宜実行されたり実行保証が必要なバックグラウンドタスクを行うArchitecture Componentです。

これまでAndroidのバックグラウンド処理を扱う物というとForeground Serviceでベッタリ書いてみたりJobScheduler, FirebaseJobDispatcher, AlarmManagerといったものをAPIレベルに応じて使い分けたりする必要があったのですが、WorkManager内でこれらを適切に使い分けてくれるようになります。

ちなみに現在はstable、というわけではなく、12/19にbeta01がリリースされました。思ったよりタイムリー。

Codelabsをやっていく

Background Work with WorkManager

Google I/O 2018のタイミングでWorkManagerが登場したのですが、それに合わせてCodelabsも公開されているので、これとにらめっこしながら概観を追いかけていくことができます。

ちなみに12/23時点でCodelabs内で指定されているバージョンが1.0.0-bata01になっているのですが、Codelabs中で扱っているコードがBreaking changeの発生した1.0.0-alpha13より前の物となっており、いわゆるコピペコードでは動作しないものになっているので注意しましょう。Codelabsで用意されているリポジトリはちゃんと1.0.0-beta01に追従されているのでそちらを参考にする場合は特に不都合ないと思います。

参考 -> Architecture Components Release Notes  |  Android Developers

ちなみにCodelabsではバックグラウンドで画像にブラーをかけるアプリが題材になっています。実際にブラーをはける処理はすでに実装されていて、WorkManagerにつないだりする部分だけを自分で加えていく形なので本質を捉えやすいかと。

f:id:e10dokup:20181223225018p:plain
題材となっているアプリ、画像を選んでそれにブラーをかけ、かかった画像をストレージに保存します

とりあえずWorkManagerで出てくる登場人物を知る

Worker

WorkManagerによってバックグラウンドで処理されるロジックを扱うクラス。
Worker をextendsし、 doWork() 中に実行されるロジックを記す。

doWork() の返り値となっている Worker.Result の返り値が1.0.0-alpha13で変更されており、Codelabs中に記されている Worker.Result.SUCCES という定数指定から Worker.Result.success() というような形になりました。後述のoutputDataもここに引数に入れることでよくなります。

WorkRequest

Workerを実行するリクエスト。 作ったWorkerを渡してRequestを作成し、WorkManagerに渡すクラス。このWorkRequestが実行キューに積まれる感じ。 OneTimeWorkRequest(一度だけ実行)/PeriodicWorkRequest(定期的な実行)があり、Constraintsを使って実行条件を指定したりできる。

WorkManager

依頼されたWorkRequestをスケジュールして実行するクラス。WorkRequestで指定したConstraintsを満たしつつ、負荷を分散しながらスケジュールしてキューに入っているWorkRequestを実行する。

簡単な扱い方

  • extends Worker なクラスを用意する
    • doWork() メソッドをオーバーライドして必要なロジックをそこに実装する
    • 処理の成功、失敗を Worker.Result.success() / Worker.Result.failure() でreturnする
  • WorkRequestに作ったWorkerを入れて、Requestを作成する
    • 特に指定がなく、一度だけ実行するだけなら OneTimeWorkRequest.from(HogeWorker.class) でOK
  • WorkManagerにenqueueする
    • 特になんてことはなく、作ったWorkRequestを workManager.enqueue(request) するだけ

Workerに値を渡す

ファイルを取得して実行するためにURI Stringとかが必要な場合はDataクラスがあるのでこれを使います。扱い方としてはIntentのExtraみたいな感じで、Data.Builder().putString("KEY", "VALUE").build() のようにし、WorkRequestを from で一発で作らずにbuilderを使って作ります。

OneTimeWorkRequest.Builder(HogeWorker.class)
        .setInputData(data)
        .build()

実際に受け取った値をWorker中で取り出すためには、Worker側で getInputData().getString("KEY")

複数の処理を連結して実行する。

CodelabsではChain your worksと書いてある節です。複数のWorkerを逐次実行することが可能で、題材となっているアプリでは

ディレクトリのクリーンアップ -> ブラー処理(強度に応じて複数回) -> ファイル保存

という一連の流れをchainしています。実装としてはWorkContinuationクラスを使います。 WorkManager#beginWith で起点となるWorkRequestを渡して、 WorkContinuation#then にそれ以降に続くWorkRequestを渡していき、最終的にWorkContinuation自体をenqueueすることで実行されます。

// AWorker -> BWorker -> CWorkerの順に実行されていくWorkContinuationの例
val continuation = workManager.beginWith(OneTimeWorkRequest.from(AWorker::class.java))
continuation.then(OneTimeWorkRequest.from(BWorker::class.java))
continuation.then(OneTimeWorkRequest.from(CWorker::class.java))
continuation.enqueue()

また、このままだと重複実行を許すので beginWithbeginUniqueWork にすることで重複実行を防ぐことができます

var continuation = workManager.beginUniqueWork(
        "unique_work_id",
        ExistingWorkPolicy.REPLACE,
        OneTimeWorkRequest.from(AWorker::class.java)
)

Constraintsを使って実行を制限する

WorkRequestのBuilderがsetConstraints()というメソッドを持っており、この中に以下のようにビルドしたConstraintsを渡すことでWorkerの実行を状態に応じて制限することができます。

/// 端末充電中でのみ実行するConstraints
val constraints = new Constraints.Builder()
                .setRequiresCharging(true)
                .build();

この他にも、NetworkTypeに応じて実行を制限できたり(METERED/UNMETEREDがあるのでおそらく従量課金かそうでないかを判断している…らしい?)、バッテリ残量、ストレージ残量に応じて実行を制限できたりします。

まとめ

寄った勢いなのでがっさり + この話は色んな人が先に触れている気がするのでなんとなくやってみたレポートでした。これ以外にもWorkerでの実行状態の取得(WorkStatus)ができたり、これまでAPI分岐を考えたり自前で実行可否の判定処理を実装していたりした部分が割とWorkManagerが吸い取ってくれそうでいい話感があります。

なんでこれを扱おうと思ったのかというと、以前勉強会で発表した資料がForeground Serviceをガッツリ使っていて、WorkManagerとか使ったらいいのに…というアドバイスを頂きまして、そのノリで「じゃあお酒飲みながら勉強してみますかー」という感じでした。

実際多分ググったほうが詳しい解説をしていらっしゃる方がいると思うのでほぼ備忘録です。ここまでお付き合いくださりありがとうございました。