位置情報を正確にトラッキングする技術 in Kotlin — (第1回) バックグランドでの位置情報トラッキングを可能にするアーキテクチャ

Taka Mizutori (JP)
Jan 5 · 14 min read

UberやNike+、Pokemon Goのようなアプリのコアとなる位置情報トラッキング技術についてiOS、Android(Java)での実現方法について解説して来ました。
https://medium.com/location-tracking-tech

Android向けにJavaのサンプルコードを使った説明はこちらから始まる4回のブログシリーズを参照してください。

Android Studioが正式にKotlinをサポートしたため、今回このJavaのコードをKotlinですべて書き換えるとともにサポートライブラリ等を使いコードをシンプルにしました。またAndroid OS Oreo(API Level 26)からバックグランドでサービスを立ち上げて継続的な処理をすることに制限がかかったため位置情報を継続して取得するLocationServiceをOreo以降のAndroid OSでも動かすための対策を入れました。

今回はKotlinのコードスニペットをつかいながら位置情報を正確にトラッキングする方法を解説を4回に渡って説明して行きたいと思います。ほとんどの部分はJava版のブログと重複しますが、最後までお付き合いください。

それでは、まずサンプルアプリの土台から作っていきましょう。

Projectの作成

まずAndroid Studioでプロジェクトを作りEmpty Activityを追加します。

LocationService class

Web上にある位置情報取得のチュートリアルやサンプルはActivityから位置情報取得を行うサンプルがほとんどですが、このやり方ですとActivityがAndroidにkillされた場合位置情報の取得も途絶えてしまいます。アプリが長時間バックグランドにいるような状態でも位置情報を取得し続けるためにはServiceを立ち上げそこから位置情報をトラッキングするのが一番確実な方法です。

そこでまずServiceの子クラスを作って位置情報トラッキングに特化したサービスにカスタマイズしていきましょう。

ファイルを配置したいパッケージを右クリックしNew > Service > Serviceからサービスクラスを作ります。

名前は下のようにLocaitonServiceとし、

LocationServiceクラスは他のアプリから参照するものではないので、Exportedフラグのチェックは外してください。

これで、Manifest.xmlにLocationServiceができていると思いますが、念のため、下のようなXMLのエレメントが追加されているか確認してください。

Permissions

次に同じManifest.xmlファイル内でパーミッションの設定をします。

ACCESS_FINE_LOCATIONだけを設定すればいいですが、開発段階では、エミューレーターからのダミーの位置情報も受信したいので、ACCESS_MOCK_LOCATIONもONにします。

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />

LocationServiceを初期化してBindする

サービスは他のクラスは、初期化と初期化下サービスへの参照の取得方法が特殊なのでここで説明します。

MainActivityのonCreate()の中でIntentを使ってLocationServiceのインスタンスを作り実行します。

onCreateのなかでLocationServiceを呼び出すためのIntentを作ります。従来であればApplicationクラスのstartService()にこのIntentオブジェクトを渡せばLocationServiceがバックグラウンドで起動しました。
しかし、Android OS 8 (Oreo, API Level=26)からバックグランドでのサービスの実行に制限がかけられ、LocationServiceを継続して実行できなくなりました。そこで、Android OSがOreo以降の場合はstartForegroundSerivce()という関数を呼び、LocationServiceをForeground Serviceとして実行します。
Oreo以降のバックグラウンドサービスの制限については以下の公式ドキュメントに詳しく記述されています。

続いて、MainActivityの中でLocationServiceオブジェクトへの参照を取得するためにはServiceConnectionというクラスのオブジェクトを介して行わないといけないので、ServiceConnectionオブジェクトのインスタンスを生成し、このクラスの中で2つのメソッドをオーバーライドします。

このServiceConnectionオブジェクトと、Intentを引数にしてサービスを”bind”します。bindとは”くっつける”という意味で、文字通りActivityにServcieをくっつけて管理できるようにすることをbindServiceは行います。

ServiceConnectionの中でオーバーライドしたonServiceConnected() の中でLocationServiceのインスタンスを取得しlocationServiceメンバー変数に参照を格納します。これでMainActivityからlocationServiceのメソッドにアクセスできるようになります。

onServiceDisconnected() はLocationServiceが何らかの理由で消滅した時に呼ばれるので、locationServiceメンバー変数にNULLを代入しておきます。

これで、Activityの中からLocationServiceにアクセスできるようになりました。

LocationManager

それではここからLocationServiceの中の実装を行って行きます。

まず、LocationServiceクラスの中にstartUpdatingLocation()というメソッドを実装します。

(前回までのiOSの方のサンプルとアーキテクチャやメソッド名などをある程度まで揃えています。)

startUpdatingLocation()の中でまずLocationManagerオブジェクトを初期化します。

val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager

Criteria

次にLocationManagerが位置情報を取得する際のルールを細かく設定しなければいけなくこれがやや複雑です。大まかにいうと

  • Criteriaを用意する
  • Minimum Time (前回位置情報を取得してから最低どれだけ待って次の位置情報を取得するか)を設定する。
  • Minimum Distance(前回位置情報を取得してから最低どれだけの距離をユーザーが移動したら次の位置情報を取得するか)を設定する。
  • accuracyをACCURACY_FINEにする
  • PowerRequirementを(to get best GPS signal)
  • altitude(高度)は今回使わないので、altitudeRequiredをfalseにする
  • locationManagerのスピード算出機能は今回使わないので、speedRequiredをfalseにする。
  • costAllowedフラグをtrueにする。このフラグをオンにするとLocationManagerが基地局とデータのやり取りをして位置情報の制度を高めることになります。基地局とのデータのやり取りが発生するのでパケット量が増えユーザーが通信事業者に支払うデータ料金が増える可能性があるため”CostAllowed”という名前になっています。
    Androidに搭載されているGPSはA-GPS(Assisted GPS)といって基地局の位置情報とそこからの距離でGPS衛星からの位置情報を補完するタイプのGPSのため、この機能をオンにするかしないかで位置情報の精度が大きく変わります。高精度の位置情報を取得するのであればオンにしなければなりませんが、パケット量に関してユーザーにフェアな設計にしたい場合は一度アラートを出してこの位置情報取得に基地局とのデータやり取りが発生してもいいか聞いてからこのフラグをオンにするというような実装をしてもいいかもしれません。
  • bearingRequiredをfalseにします。. “Bearing”とは、デバイスが指し示している方向のことです。今回方向の情報は使わないのでfalseにします。
  • horizontalAccuracyとverticalAccuracyをHIGHに設定します。

最後にLocationManagerのrequestLocationUpdatesを呼びます。この時Minimum Timeを1秒、Minimum Distanceを1メートルに指定します。また先ほど作ったCriteriaもパラメーターとして渡します。
Minimum TimeとMinimum Distanceは位置情報取得時のコールバックが頻繁に呼ばれるのを避けるためです。位置情報取得後に様々な描画処理や計算処理をする場合はMinimum TimeとMinimum Distanceを設定しないとコールバックが頻繁に呼ばれてバッテリが消費されるので適切なMinimum値を設定するのは重要です。
(Minimum TimeとMinimum Distanceはあくまで上位レイヤーでのフィルタであり、実際のGPSデバイスは、Criteriaで設定された基準を満たすためにより頻繁に位置情報を取得しているので、Minimum TimeとMinimum Distanceを大きくしてもGPSデバイス自体の消費量を少なくすることはできません。)

今回はNike+のようなランニングアプリに必要な精度を出すためにMinimum TimeとMinimum Distanceを1秒&1メートルに設定しましたが、Uberのようなアプリだと3秒&10メートルくらいでもいいかもしれません。

それ以外にやることとしては、自分自信をlocationManager.addGpsStatusListener(this)によってGPSStatusListenrとして登録します、またrequestLocationUpdatesの第4パラメータに自分自身を渡すことによってLocationListnerとして登録します。

LocationListener

Location Providerという言葉がありますが、これはGPSかWifi Networkどちらが位置情報を供給しているのかという供給元の情報です。LocationListenerインターフェースを介してこのLocation Provider(位置情報の供給者)の情報を逐一取得することができます。

GPSStatus.Listener

GPSStatusListenerは下のような下位のGPSエンジンの情報を取得するためのインターフェースです。.

  • GPS_EVENT_STARTED
  • GPS_EVENT_STOPPED
  • GPS_EVENT_FIRST_FIX
  • GPS_EVENT_SATELLITE_STATUS

今回のサンプルコードでは、これらの情報は使用しませんが、衛星の取得状況などをモニタしたい場合はここに実装を加えることでできます。

Run the app

ここまでできたら、Emulatorでアプリを走らせてみます。

シミュレーターのメニューバー(縦長でアイコンが並んでいるもの)の一番下の“…”をクリックするとExtended controlsというダイアログが開きます。ここでダミーの位置情報を送ることができます。ダミーの緯度、経度情報を入力するとonLocationChanged()が呼ばれ位置情報がログキャットに表示されると思います。

(同じ位置情報を続けて送ってもLogcatには最初の位置情報しか表示されません。これは、Minimum Distanceのフィルタが効いているからです。適当に緯度/経度の値を変えて送ってみましょう。)

GPXとKMLファイルを取り込むこともできます。マラソン大会のGPX、KMLファイルはネット上に落ちているので試しにここからロードして一番下の再生ボタンを押すとマラソンでの走りを再現することができます。(サンプルプロジェクトのトップディレクトリの下にHalf_Marathon.gpxという名前でGPXファイルが入っているので使ってみてください。)

下の部分でGPX/KMLのロードとコントールができます。

サンプルは下のRepositoryにあります。こちらは完成系ですべての機能を含んだコードとなっていますが、ここまで自分で実装してみて、なにか動かないことがあればこちらのコードと比較してみてください。

次回は、マップにトラッキングした経路や自分の位置、さらには今現在の位置情報の精度を示す円などをマップ上に描画する方法を説明します。デバッグ目的でもマップへ位置情報をビジュアライズするとデバッグのヒントがたくさん得られるので位置情報取得のさらに深いテクニックを説明する前にサンプルアプリにマップ表示の機能を搭載したいと思います。

(第2回)走行データをGoogle Mapにマッピングする


このブログの内容に関する質問や位置情報トラッキング機能開発に関するご相談などはこちらにご連絡いただければと思います。
@mizutory
mizutori@goldrushcomputing.com

位置情報を正確にトラッキングする技術

iOS、Androidで位置情報を正確にトラッキングする技術に関してまとめて行きます。

Taka Mizutori (JP)

Written by

Goldrush Computing Inc. 代表。iPhone/Androidアプリのプログラマー(Objective-C, Swift, Java)、時々ロボット(Pepper/Nao)の振り付け(Choreographe+Python)。前職はSonyEricsson。丘に上がったサーファー。3児のパパ

位置情報を正確にトラッキングする技術

iOS、Androidで位置情報を正確にトラッキングする技術に関してまとめて行きます。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade