React NativeでiOSアプリのバックグラウンド・タスクを実装する
ウィジェットに表示するデータを定期的に更新するために、React Nativeでバックグラウンド・タスクを実装する方法について調べてみました。
情報量が多いのでまずはiOS編です。
はじめてこのブログを読んだ方へ: U-motionとは?
U-motionは牛の首につけたセンサーを使って活動内容を記録、AIの力で健康状態を解析して畜産農家さんをサポートするモニタリング・システムです。ウェブ・サービスとモバイル・アプリをReactで実装しています。このブログではU-motion開発チームが開発中に得た技術的な知見を公開しています。
https://www.desamis.co.jp/product/
課題とゴールの整理
弊社で開発している『UM Utils』というアプリケーションではユーザーの農家さんに最新のアラートをお知らせするためのウィジェットを表示できます。このアラート情報を定期的に更新するためにバックグラウンド・タスクを利用します。
U-motionウィジェット
要件はこんな感じです。
・バックグラウンド・タスクでウィジェットに表示するデータを更新する
・データはHTTP API経由で取得する。
・認証が必要なためAPIリクエストはReact NativeのJS側で実装したい。
・15分に一回程度の更新を目指す。
iOSのウィジェットはSwiftUIコードから定期的にAPIリクエストを実行してデータを更新することができますが、今回使用するAPIは認証が必要です。JS側で実装されたセッション管理の仕組みを流用したかったのですが、SwiftUIコードからReact Nativeレイヤーを呼び出す仕組みを見つけられず、ポーリングによる実装を選択しました。
iOSネイティブ・レイヤーのバックグラウンド処理
まずはiOSが提供しているバックグラウンド処理を実行するためのAPIについて調べていきます。こちらのQiitaの記事が参考になりました。
紹介されていたのは下記のAPIです。
Background Task Completion
フォアグラウンドで開始した作業をアプリがバックグラウンドに移行したあとで継続して実行する。ファイル書き込みなど作業中にアプリが終了すると困るタスクのための「延長」処理です。
BackgroundTasks (App Refresh Tasks)
BackgroundTasks (Processing Tasks)
バックグラウンドでタスクを実行するためのAPI。指定した時間に30秒程度の実行が許可される。アプリ起動まえのデータ・ロードなど体験向上を主眼していて、必ず実行されると保証されているわけではない。
・・・
今回は扱いませんが、ほかにこんなAPIも紹介されていました。
Background Notification (Silent Push)
iOS。Dataオンリーの通知を送信することでプッシュ通知をトリガーにしてアプリの処理を呼び出すことができます。
Background URL Session
Discretionary Background URL Session
ダウンロード処理などをバックグラウンドで実行します。
iOSのバックグラウンド・タスクの難しさ
調べてみるとiOSでのバックグラウンド・タスクには以下のようなチャレンジがありました。
・iOSがプライオリティを調整していて、タスクが実行される保証がない
・アプリがterminateの状態ではタスクが実行されない
バッテリ消費を抑制する意図もあるかと思いますが、バックグラウンド・タスクはアプリ起動前にデータをロードするなど体験を向上する目的が主でアプリを利用しないとプライオリティが下げられて実行されなくなるなど挙動に注意が必要なことがわかりました。
その状況を踏まえた上で、バックグラウンド・タスクを利用するために最適のパッケージを調べていきます。
React Nativeでバックグラウンド処理を扱うパッケージと使われているAPI
React Native向けに公開されているバックグラウンド処理のためのパッケージを探してみました。
1. expo-background-fetch
Expoが提供するバックグラウンド・タスク管理用のモジュールです。iOSでは後ほどご紹介する react-native-background-fetch と同じ App Refresh Tasksを使います。
ドキュメント内にXcodeのInstrumentsを使ってbackground fetchをデバッグする方法が書かれており、このモジュールを利用しなくても参考になります。
2. react-native-background-fetch
利用しているiOSのAPIは恐らく BackgroundTasks (App Refresh Tasks) とBackgroundTasks (Processing Tasks)の2種類ですが、このモジュールはタスク管理を独自のライブラリで実装していてその部分のコードは公開されていないようです。
3. react-native-background-timer
Background Task Completion APIを使って、アプリ終了後もsetTimeoutによりタイマーコードを継続して実行します。タイマーで設定した時間になると指定されたハンドラを実行、再びタイマーをセットしています。
サンプルアプリケーションの実装
タイマー処理を実機で確認するためにサンプル・アプリケーションを実装しました。Realmを使ってバックグラウンド・タスク起動時にログを記録、React Native Notificationsを使ってローカル通知も送信します。
結果: 実装方法によってタイマーイベントが発火される精度に微妙な違いがある。いずれの実装もアプリがKillされるとタイマーは動かない
このテスト・アプリを自分の端末にインストールして、しばらく「一緒に暮らして」みました。タイマーが発動すると通知が届くので稼働の状況を体感できます。ログも残るので、ちゃんとデータでの検証もできます。
react-native-background-timerとreact-native-background-fetchは利用しているiOSのAPIが異なり、結果としては react-native-background-fetch がより規則正しくイベントをトリガーできていました。
ただreact-native-background-fetchはドキュメントに
iOS can take days before Apple's machine-learning algorithm settles in and begins regularly firing events.
(iOSでは数日経ってAppleの機械学習アルゴリズムが落ち着くまで定期的にイベントを発火しない場合がある)
とあるように、イベントが発火されるようになるまで時間がかかりました。 react-native-background-timerはアプリ実行開始からすぐにタイマー・イベントをトリガーできているので、2つのパッケージを併用して異なるAPIを組み合わせることでより安定したタイマー処理を実装する選択肢もありだと思います。
前述したようにアプリをterminateするとタイマーの動作も停止して、テストアプリから通知が来なくなりました。また夜寝ている間にもタイマー起動が抑制されているような挙動がありました。
まとめ
バックグランド処理を継続的に規則正しく起動することは、なかなか難しいことが分かりました。ただ、ログを見るためにサンプルアプリを1日に何度か起動しながら使っていた範囲では、実用上問題ない範囲でタイマーによる情報更新が機能する感触もありました。
当面はこの手法によるデータ更新を継続しつつ、サイレント・プッシュによるデータ更新やSwiftレイヤーでのAPIアクセス実装など異なるアプローチを試してみたいと思います。
次回、Android編に続きます。
🐮🐮🐮
デザミス株式会社では一緒にU-motionを作ってくれるエンジニアを募集中です。
• フロントエンドエンジニア
React, Vue.js等のフレームワークを用いた開発および運用の経験がある方
• バックエンドエンジニア
RDBMSの基礎知識とRuby on Railsを用いた開発および運用の経験がある方
• IoT エンジニア
OSとネットワークの十分な知識とGo, C, Python等を用いた開発経験がある方
• データエンジニア
統計の基礎知識とPandas, NumPy, scikit-learn等を用いた分析経験がある方
ご興味がありましたら、カジュアル面接からでも可能ですので、希望を添えてお気軽にご応募ください!
現在全部のステップをオンラインで実施しており、地方からでも参加可能です。エンジニア一同お待ちしております🐮
はじめてこのブログを読んだ方へ: U-motionとは?
U-motionは牛の首につけたセンサーを使って活動内容を記録、AIの力で健康状態を解析して畜産農家さんをサポートするモニタリング・システムです。
🐮