[iOS] バッテリー消費を抑えつつ、位置情報を定期的に収集する方法

ルナスコープiOSアプリは、バックグラウンドで定期的に位置情報を収集しています。(ユーザが位置情報取得を許可した場合)

位置情報の定期取得に関するノウハウが書かれた日本語の記事があまりないので、ルナスコープの開発で得た知見を共有します。

※位置情報を取得するためのプロジェクトセットアップや実装の詳細は他サイトを参考にしてください

バックグラウンドで定期的に位置情報を収集する

iOSアプリは、基本的にバックグラウンドでの動作をさせてくれません。ただし、位置情報収集の場合は違います。

次のメソッドを呼び出し、位置情報を取得し続けることができます。

LocationManager.startUpdatingLocation()

そして、実機でテストして「バッテリー消費が激しすぎる!」という課題に多くの人がぶつかります。。

バッテリー消費を抑える

バッテリー消費を抑える基本は、Appleの公式ドキュメントに書かれています。

Reduce Location Accuracy and Duration

ただし、上記方法は高精度で位置を取り始めたら、高精度で位置を取り続けることを基本とします。例えば、5分毎に位置情報を取得したいとき、5分に1度動作するというようなことを想定していません。ルナスコープが期待する動作はむしろこちらでした。

そこで、調べると次のようなアイデアを見つけました。

Timerで定期取得するタイミング以外は desiredAccuracy に kCLLocationAccuracyThreeKilometers, distanceFilter に 99999 をセットするバッテリー消費が抑えられる

Periodic iOS background location updates

この方法をルナスコープでも採用しています。

つまり、定期的に位置情報を収集し続けるなら次のようなフローになります。

  1. Accuracy等を高精度に設定
  2. startUpdatingLocation() 呼ぶ
  3. Timerセット
  4. Accuracy等を低い精度に設定

そして、タイマーから呼び出された時に、再度、Accuracyを高精度にすれば良いです。

Lunascopeでの応用

Lunascopeは停止している時は頻度を少なく、移動している時は頻度を多く位置情報を取得しています。バッテリー消費を抑えるために

LocationManager.stopUpdatingLocation()

を呼ぶのですが、こいつを呼ぶとTimerが止まります!(重要)

スマホが移動していない(停止している)ときに、stopUpdatingLocation を呼ぶことでバッテリー消費を抑えつつ、どうやって次回移動を検出するかです。ルナスコープでは次のような方針としました。

  • MonitoringSignificantLocationChanges によるイベントを待つ
  • Background Fetch によるイベントを待つ
  • Geofence によるイベントを待つ

1時間毎等の定期的な取得は不可能ですが、最悪アプリが全く位置情報を取らなくなるというのを避けれるはずです。それぞれ、簡単に説明いたします。

MonitoringSignificantLocationChanges によるイベントを待つ

LocationManager.startMonitoringSignificantLocationChanges()

上記メソッドを予め呼び出しておけば、大きな移動があった時にコールバックが呼び出されます。それなので、このメソッドはどこかで呼んでおきます。

Background Fetch によるイベントを待つ

Background Fetchは、OSが良いと思うタイミングで呼び出すのでほぼ制御不可能です。
そもそも、ユーザの行動を学習して、ユーザが使うであろう時刻の少し前にBackground Fetchがアプリを起動し、アプリのキャッシュ作成を手伝う目的だったので仕方ありません。細かい制御はできませんが、一応、バックグラウンドで呼び出してくれるのでセットしています。

Geofence によるイベントを待つ

これが結構便利です。Geofenceは緯度、経度、半径、を指定して、イベント発生の領域を仮想的に設置できます。バッテリー消費は少なめですが、移動を検知できます。ルナスコープでは、スマホが停止したと判断したら、その位置にGeofenceを張るというロジックにすることで、ユーザの移動をより素早く検知できるようにしました。(とはいえ、数分遅れます。)

ざっくりと処理をフローにすると次のようになります。

これで、バッテリー消費を抑えつつ、スマホの移動をこまめに追跡できるようになりました。

OS起動時にアプリを起動する方法

Androidで言うところのBOOT_COMPLETEDイベントみたいに、iOSでもOS起動時にプログラム実行できないかなーと探しましたが、なんの成果も得られませんでした!!(多分無い)

ただし、次の3つのケースでは、OSシャットダウン前の動作が有効で、再起動後でもイベントが発生するようです。

  • VoIPを使っている(スマホの電源ONなら、常にプロセスが残るらしい)
  • シャットダウン前にGeofenceを設置していた
  • シャットダウン前にstartMonitoringSignificantLocationChangesを呼んでいた

VoIPについては辛い思いをしたので使用をご検討の際はこちらの記事「 [iOS] 6度もリジェクトされた理由 」をご確認ください。

残りの2つをルナスコープでは利用していますので、OS再起動後もユーザが移動してくれれば、位置情報を定期的に取得できるようになっています。

皆様のご参考になれば幸いです。

ルナスコープ公式サイト

iOS版ルナスコープ

Android版ルナスコープ