はじめに
皆さん、こんにちは!mikanでAndroidエンジニアをしているgumioです。
今回はmikan androidにWorkManagerを導入しようとした際に色々面白かったので、それを紹介しようかなと思います。
こちらの記事は以下のような流れになっています。
- WorkManagerを知らない人向けに簡単な概要とメリット
- WorkManagerのユースケース
- WorkManagerの内部挙動
- 検証してtoolなどを使って覗いてみた
- さいごに
WorkManagerを知らない人向けに簡単な概要とメリット
あるバックグラウンドタスクを実行するScheduler APIです。
今までバックグラウンドタスクに利用されていたFirebaseJobDispatcher、GcmNetworkManager、JobSchedulerなどがあったが、WorkManagerを利用すれば以下の図のように内部で分岐してくれるので、API levelとかを意識する必要がないメリットがある + 電池寿命も考慮されており、電池寿命が伸びるメリットもあると公式は言うております。
ちなみにWorkManagerはAPI level 14
から機能するので、安心してください。
WorkManagerの全体的なメリットとしては
- API level 14から気にせずに機能する
- シンプルで一貫性がある
- 信頼性の高い非同期タスク
- 電池寿命がよくなる
- RxとCoroutineのサポートもされている
今回WorkManagerの詳細な使い方などは日本語ドキュメントが充実しているため、紹介しないです。
WorkManagerのユースケース
Coroutineとはどこで使い分けたらいいの?どういう時にWorkManagerを使うべき?とか少なからず思いますよね。
androidドキュメントににバックグラウンドタスクのタスクのガイドが用意されてますので、これに従うでいいと思います。
僕なりの解釈としてはこんな感じだと思いましたが、どうでしょう?
- 短時間 or 長時間かかって、裏側で必ず動かしてほしいタスク
- 厳密には書かれていませんが、10 分以上は実行できるらしい
- 電源が急に落ちる、いきなりプロセスキルされても必ず実行されたい 延期させたい: ネットワークがオフラインになっている、充電が充分にない場合に延期させて、条件を充分に満たした場合に実行したい
公式でも以下のように述べられてます
WorkManager は、ユーザーが画面から移動した場合、アプリが終了した場合、デバイスが再起動された場合でも、確実に実行する必要がある作業を対象としています。次に例を示しますログやアナリティクスをバックエンド サービスに送信するアプリデータをサーバーと定期的に同期する
mikanでもログを送るときに使用しようとしています。
アプリデータをサーバーと定期的に同期するはfetchしてくるイメージだと思います。アプリのデータをpushすることもできなくはないですが、WorkManagerが入力を受け付けれるをデータ量は10KBまでとなっているので、あまり何かを載せるのは期待できません。
それでは、次に実際の挙動を見ていきましょう!
WorkManagerの内部挙動
さて、今回一番紹介したい部分で、WorkManagerの謳い文句の
「ユーザーが画面から移動した場合、アプリが終了した場合、デバイスが再起動された場合でも、確実に実行する必要がある作業」
これって、結局どうやってるんだろう??ってなりました。 そこで内部の動きを簡易ではありますが、追ってみます。
まず、簡単なWorkManagerの流れのおさらいですが
- 行いたいタスク(Worker)を定義する
- このタスクの実行方法とタイミングを決めるRequestを作って依頼する
- WorkManagerを通じて、システムに2.のRequestを飛ばす(enqueue)
すごくシンプルですね。
このとき、WorkManagerのStateは以下の図のようになっていて、一度きりのRequest(OneTimeWorkRequest)と定期的なRequest(PeriodicWorkRequest)で少し異なりますが、終わりがあるかないかの違いだけで基本は同じです。
以下、処理の状態 | Android デベロッパー | Android Developers より
それでは、enqueueされたあとのRequestたちはどのように扱われてるのでしょうか?
- Internal TaskExecutorが、requestされたものをWorkManager DB(Room)にWorkRequest情報を保存します
- Internal TaskExecutorはsingle threadのExecutorで、実際にenqueueされたrequestをさばく役割を持っています
- Executorを知らない方はこちらへ
- Internal TaskExecutorはsingle threadのExecutorで、実際にenqueueされたrequestをさばく役割を持っています
- WorkRequestのConstraintが満たされると(すぐにでも可能ですが)、Internal TaskExecutorはWorkerFactoryにWorkerを作成するように指示します
- その後、defaultのExecutorは、mainThreadからWorkerのdoWork()メソッドを呼び出します
なるほど!RequestされたものがDBに保持されていたんですね。このDBにはRoomが使われてました!! Requestされたものは制約が満たされるまではDBでおとなしくしてるんですね。この制約は端折っていましたが、Requestを作るときに指定することができます。
例えば、ネットワークがオンラインのときかつ、充電中のときのみ実行される制約が設定されます。
val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresCharging(true) .build()
先程のStateでRUNNINGになってたとしても処理が完了する前に電源落とされたり、オフラインになったりするとENQUEUEEDに戻り、またDBに大人しく返ってたんですね。
改めて、最初の謳い文句を再度見たときに、なるほどとなるのではないでしょうか?
「ユーザーが画面から移動した場合、アプリが終了した場合、デバイスが再起動された場合でも、確実に実行する必要がある作業」
検証してtoolなどを使って覗いてみた
最後に少しだけ挙動を確認してみて、終わりにしようと思います! 以下のような1...10を出力する簡単なWorkerを作成します。
→ 例ではWorkerではなく、CoroutineWorkerを使用していますが、こうするだけでdoWorkはsuspend関数となり、内部ではCoroutineが使われるようになりますb
class TestWorker( appContext: Context, workerParams: WorkerParameters ): CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { repeat(10) { Log.d("TestWorker", it.plus(1).toString()) delay(1000L) } return Result.success() } }
次にこのWorkerをRequestします。一度きりでネットワークに繋がってるときのみの制約にしています。
補足: 前述したようにWorkerをRequestするときは、一度きりと定期的で分かれます。
また、実行条件の制約をつける場合はConstraints.Builderで指定し、setConstraintsにsetしてあげる必要があります。
val testWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<TestWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
).build()
最後に適当なボタンを用意して、enqueueするようにして、実行してみましょう 途中でオフラインにしてみます。
Workerがキャンセルされてることが確認できますね。 このとき、最近機能が充実してAndroid StudioのApp Inspectionで覗くことができるので、見てみましょう!!
App Inspectionとは、RoomやWorkManagerといったdebugをしづらいものをAndroid Studioがdebugしやすいように用意してくれたサポートtool郡のことを指します。
まずはDB Inspector
どうやら、内部でWorkManager用にworkdbというのが内部で自動作成されていることがInspectorからわかりますね。これが先程の内部挙動で紹介したRequestされたWorkerを保持しておくために必要なDBということが予測できます。
色んなtableがありますが、試しにWorkSpecというtableを開いてみましょう!
名前の通り、各Workerの詳細情報を持っていることが伝わりますね。
次にArctic Fox Canary 3で新たにWorkManagerのdebug用に追加された、Background Task Inspectorを見てみましょう。
最後のWorkerが途中でオフラインにされたWorkerなのですが、ENQUEUEDになっていることがわかります。 この状態でアプリをオンラインにして再度立ち上げたら、再度Workerが動き出して無事に処理されました!!
さいごに
いかがだったでしょうか? 今までWorkManagerとは関わる機会がなかったのですが、今回導入することをきっかけにキャッチアップをし、気になるところなど、学んだことを書いてみました。
mikan androidではこのように新しめな技術を気軽に試して、導入できるかどうかを検証するのも業務の一環です。まだまだやりたいことがたくさんあり、メンバーが足りないので少しでも興味を持った方は是非お話しましょうー!