位置情報を正確にトラッキングする技術 in iOS — (第3回) バックグランドでのトラッキング、精度、バッテリー消費
前回のブログまでで、位置情報の取得はできるようになりましたが今回はアプリがバックグラウンドにいるときの位置情報の取得、取得する位置情報の精度の設定について説明します。バッテリー消費量についても触れたいと思います。
前回の投稿でLocationServiceクラスというクラスを作ってそこで位置情報系の処理を行うようにしました。これをAppDelegateのdidFinishLaunchingWithOptionsの中で初期化したのでインスタンスは1つですが、構造的にもLocationServiceのインスタンスが1つしか生成されないようにこのクラスをまず下のようにSingletonにします。
そしてAppDelegate内で下のように位置情報の取得をshared instanceを介して行うようにしました。
バックグランドでの位置情報取得
Nike+のようなランニングアプリではアプリがバックグラウンドにいる時も位置情報をとる必要があります。バックグラウンドでの位置情報取得を可能にするためには、まず下のようにInfo.plistファイルの中のUIBackgroundModesというKeyにlocationという値を設定します。UIBackgroundModesはPlistエディタ上ではRequired background modeという名前で表示されます。
ここからはLocationService.swiftのコードを編集していきます。
LocationManagerのallowsBackgroundLocationUpdatesを下のようにtrueにします。
locationManager.allowsBackgroundLocationUpdates = true
これでバックグラウンドからの位置情報取得が可能になりました。
バックグラウンドモードとバッテリー消費
LocationManagerクラスの中にpausesLocationUpdatesAutomaticallyというフラグがあります。trueにするとバッテリー消費量を抑えるということで設定することがAppleのドキュメントなどでは推奨されています。
locationManager.pausesLocationUpdatesAutomatically = true
このフラグをtrueにするとiOSが位置情報を新たに取得する必要がない状況を自動的に判断してくれ、位置情報取得をポーズしてくれるというものです。
iOSは加速度センサーなどのセンサーの値を見て状況を判断し、ユーザーに新しい位置情報の提供をする必要がないと判断する(例えばユーザーがずっと同じ位置に止まっている時など)と位置情報の取得をポーズします。
LocationManagerにactivityTypeという変数がありこれを設定すると上記のiOSが自動的にポーズするための判定基準を与えることができます。
下のようにactivityTypeにCLActivityTypeの定数を設定する形で設定します。
locationManager.activityType = .fitness
activityTypeには以下の4つの設定ができます。
- AutomotiveNavigation (カーナビなど車のトラッキングを目的としている場合)
- CLActivityTypeFitness (ランニングやサイクリング、ウォーキング)
- CLActivityTypeOtherNavigation (その他の交通手段。ボート、電車、飛行機など)
- Other (そのほかの移動手段。移動手段のユースケースがわからない時はこれを設定します。)
pausesLocationUpdatesAutomaticallyとactivityTypeの組み合わせはバッテリー消費量を抑えるのに非常に効果的ですが、一つだけ落とし穴があります。
pausesLocationUpdatesAutomaticallyがtrueに設定されていて、アプリがバックグラウンドにいる時にiOSが自動的にポーズを行なった場合、アプリがフォアグラウンドにくるときしか位置情報取得は再開されません。
Uberのようなアプリではこれくらいがちょうど良い振る舞いだと思いますが、Nike+などのランニングアプリでは問題です。
例えば、ジョギング中にコンビニに寄って休憩をしている時に、バックグラウンドのアプリが自動ポーズを行なってしますうと、その後走った距離が記録されないというようなことが起こります。
ランニングアプリのようなアプリではpausesLocationUpdatesAutomaticallyをfalseにしておいた方が良いと思います。(pausesLocationUpdatesAutomaticallyをfalseにした時はactivityTypeの設定をする必要はありません。)
ジョギングアプリのようなユーザーの移動距離を正確にバックグラウンドでも取り続けなければならないアプリではこのフラグはオフにしましょう。
Uberのように、アプリをユーザーが開いた時に位置情報の取得を再開してくれればいいようなケースではこのフラグをオンにして消費電力を抑える方がいいと思います。
位置情報の制度とバッテリー消費量
LocationManagerのdesiredAccuracyはどのレベルの精度の位置情報をアプリが必要とするかを設定する変数です。Pokemon Go、Nike+、UberのようなアプリではkCLLocationAccuracyBestForNavigationという最も精度の高い位置情報を要求する設定が適していると思います。
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
この設定でアプリを動かすと、iOSが取得できうる最高精度の位置情報がものすごい頻度で取得できます。この状態でサンプルを動かしてみると毎秒のように位置情報が入ってくるのがわかります。Pokemon Goのようにユーザーの位置や動きをキャラクターを使って自分の分身のようなアニメーションをさせるときはこのような状況で位置情報を取り続けるのがいいでしょう。
Nike+のようなジョギングアプリは使ってみるとわかりますが、数メートル走るごとに距離が更新されているのがわかります。このようなアプリではユーザーが実際にある程度(5〜10メートル)移動するごとに位置情報が取得できれば成立します。Uberも同じです。
こういう時に便利なのがdistanceFilterです。LocationManagerのdistanceFilterに距離をメートル単位で設定すると、その距離を移動した時にだけlocationManager:didUpdateLocations:に位置情報を渡してくれます。
位置情報取得後に様々な計算処理をする場合は、このコールバックが頻繁に呼ばれてバッテリが消費されるのを防ぐことができます。
(distanceFilterはあくまで上位レイヤーでのフィルタであり、実際のGPSデバイスは、desiredAccuracyで設定された設定により頻繁に位置情報を取得しているので、distanceFilterを大きくしてもGPSデバイス自体の消費量を少なくすることはできません。)
ジョギングアプリやUberのようなアプリはdistanceFilterを設定しないと距離やペースメーターが高速に更新されたり、車のアイコンの位置が1秒間に何度も微妙に変わるようなことが起きてしまい、ユーザーの目にも見にくいものになってしまうのでバッテリ消費効果が低い場合でもdistanceFilterを設定しておいた方が良い場合が多いです。
もう1点注意する点としてdistanceFilterはそれほど正確ではありません。例えばdistanceFilterを5メートルに設定しても必ず5メートルで位置情報が取得されるとは限りません。4メートル移動したら取得される時もあれば8メートルくらい移動するまで取れないこともあります。ですので、もし5メートル移動するまでに最低でも1回は位置情報が欲しいというような場合ではdistanceFilterは2、3メートルなどに設定して、didUpdateLocationの中で自前の距離フィルタをかけるなどの方法をやることもあります。(この方法については以降の連載でまた詳しく説明します。)
最終的にLocationService.swiftのinitコードは以下のようになりまた。
これで位置情報がフォアグラウンドにいてもバックグラウンドにいても5メートル移動するごとに最も精度の高い位置情報が取得されます。自動ポーズ機能は発動されません。
ここまでのサンプルは以下のGitHubレポジトリのコミット
76ad5aed71600b1f306176baecf54e7ec9ddb892で見ることができます。
https://github.com/mizutori/iOSLocationStarterKit
次回は、マップに位置情報をビジュアライズする方法を説明します。デバッグ目的でもマップへのビジュアライズはよく使うので位置情報取得のさらに深いテクニックを説明する前にサンプルアプリにマップ表示の機能を搭載したいと思います。
このブログの内容に関する質問や位置情報トラッキング機能開発に関するご相談などはこちらにご連絡ください。
@mizutory
mizutori@goldrushcomputing.com