Corredor

ウェブ、プログラミングの勉強メモ。

cordova-background-geolocation-lt でアプリがバックグラウンドになっても位置情報を送信する

HTML5 Geolocation: Bringing Location to Web Applications

HTML5 Geolocation: Bringing Location to Web Applications

cordova-background-geolocation-lt という Cordova プラグインは、位置情報を取得し、任意のサーバに情報を送信できるプラグインだ。このプラグインの特徴はなんといっても、アプリがバックグラウンド動作に切り替わっても位置情報の取得・送信を続けるところであろう。名前のとおり、Background でも Geolocation による LT : Location Tracking を行う、ということだ。

今回はこのプラグインの使い方を説明する。想定する環境は iOS のみだが、Android の方が iOS より柔軟に設定ができるようだ。

プラグインのインストール

cordova-background-geolocation-lt のインストールは以下のようにする。

$ cordova plugin add cordova-background-geolocation-lt

初期設定と起動

プラグインをインストールすると、window.BackgroundGeolocation というグローバル変数でプラグインの API が使えるようになる。

予め on() でイベントハンドラを設定し、そのうえで configure() メソッドを使って設定適用と位置情報の取得を開始させることができる。

// 位置情報を取得した際に発火するイベント
// 位置情報を取得する周期は configure() にて設定する・位置情報の取得時に HTTP 送信も行われる
window.BackgroundGeolocation.on('location', (location, taskId) => {
  console.log(location);
  // タスクを終了させる
  window.BackgroundGeolocation.finish(taskId);
}, (errorCode) => {
  // Location Service が無効な場合などに発火する
  const locationError = {
    0: 'Location Unknown (位置不明)',
    1: 'Location Permission Denied (位置情報の取得許可なし)',
    2: 'Network Error (ネットワークエラー)',
    408: 'Location Timeout (位置情報取得処理がタイムアウト)'
  }[errorCode] || errorCode;
  console.log(locationError);
});

// 位置情報を HTTP 送信し、レスポンスを受け取った際に発火する
window.BackgroundGeolocation.on('http', (response) => {
  console.log(response.responseText);
}, (response) => {
  console.log(response.status);
});

// デバイスの位置が停止している場合に heartbeatInterval の設定値に従って発火する
window.BackgroundGeolocation.on('heartbeat', (params/) => {
  // heartbeat イベント自体は位置情報を取得しないため HTTP 通信も発生しない
  // ハートビートにより位置情報を再送信したい場合は、getCurrentPosition() を実行する
  // getCurrentPosition() で位置情報を取得すると location イベントが発火し、HTTP 通信も発生して http イベントが発火する
  // params.location.isMoving を見て、直近の位置情報が動いていなければハートビートとして位置情報の取得・送信を行う、などしても良い
  window.BackgroundGeolocation.getCurrentPosition((location) => {
    console.log(location);
  }, (errorCode) => {
    console.log(errorCode);
  });
});

// デバイスが止まったり動き出したりしたとき
window.BackgroundGeolocation.on('motionchange', (isMoving, location) => {
  console.log(isMoving ? '移動中' : '停止中');
});

// 動作の種類が変化したとき
window.BackgroundGeolocation.on('activitychange', (event) => {
  // アクティビティの種類を和訳する
  const activity = {
    'still': '停止中',
    'on_foot': '徒歩移動',
    'in_vehicle': '車移動',
    'on_bicycle': '自転車移動',
    'running': 'ランニング'
  }[event.activity] || event.activity;
  console.log(activity + ' : ' + event.confidence + '%');
});

// 設定オブジェクトを指定して位置情報取得処理を開始する
window.BackgroundGeolocation.configure({
  // 位置情報の正確さ : 0, 10, 100, 1000 の4つ・0 が正確だが電池を使う・1000 は正確さに劣るが省エネ
  desiredAccuracy: 0,
  // 移動イベントを発火させるために待機する最低移動距離 (メートル)
  // Android だとこれを 0 にした上で locationUpdateInterval を指定することで、指定秒数ごとに Location を取得できるが、iOS ではできない
  distanceFilter: 10,
  // [iOS Only] 停止状態から動き始めたと検知するまでの最低移動距離
  stationaryRadius: 25,
  // 動作の種類の変化を検知するインターバル。0 にすると可能な限り早く行動検知を行う。デフォルトは 10000 (10秒)
  activityRecognitionInterval: 1000,
  // 停止状態になってから位置情報サービスを停止するまでの間隔 (分)
  stopTimeout: 5,

  // HTTP 送信する対象の URL
  url: 'http://my-location-server.example.com/',
  // 任意のパラメータを渡したい場合は指定する
  params: {
    'some-token': '任意のトークンを付けるとか'
  },
  // 任意の HTTP ヘッダを付与したい場合は指定する
  headers: {
    'X-HOGE-FUGA': 'foo-bar'
  },
  // HTTP 通信時のメソッド指定。通常は 'POST' 送信で良いが、対象のサーバにあわせて 'PUT' を選択することも可能
  method: 'POST',
  // 位置情報を取得した順に送信するようプラグインに自動調整させる
  autoSync: true,
  
  // 本プラグインがローカルの SQLite DB に位置情報を残しておく日数
  maxDaysToPersist: 3,
  // ローカルの SQLite DB に保存しておく位置情報の件数。-1 で無制限、0 で保存させない
  maxRecordsToPersist: 50,
  // アプリを閉じてもトラッキングを続ける
  stopOnTerminate: false,
  // デバイスの再起動後にトラッキングできるようにする
  startOnBoot: true,
  // heartbeat イベントを発火させるまでの秒数
  heartbeatInterval: 10,
  // [iOS Only] heartbeatInterval を指定する際は true にし、停止時のサスペンドを禁止する
  preventSuspend: true,
  // true にするとバックグラウンド動作時に通知を表示する
  debug: false
}, (state) => {
  console.log('初期設定完了');
  
  // 通常はこの時点で位置情報の取得も開始される
  // もし位置情報の取得状況が停止状態の場合は開始させる
  if(!state.enabled) {
    console.log('位置情報取得開始');
    window.BackgroundGeolocation.start();
  }
}, (error) => {
  console.log('初期設定・起動失敗');
});

特に気になるのは location イベントと http イベントだと思うが、これらは「位置情報を取得した後」「位置情報を送信し、レスポンスを受け取った後」に発火するだけなので、基本的にはログ出力ぐらいしかやることがない。

heartbeat はハートビートを検知した場合のイベントだが、このイベント自体は位置情報の取得・送信を行わないので、必要であれば getCurrentPosition() を叩いて位置情報を再取得する。motionchangeactivitychange も状態の変化を知るのみで、このイベント自体が発火しても位置情報の取得などは行われていないので注意。

その他、GPS や Wi-Fi などの設定が変わった時は providerchange イベント、ジオフェンスのイベント検知は geofence および geofenceschange、スケジュールに沿ったイベントの発火は schedule で検知できたりする。

on() で設定をしたら、configure() で設定の適用と位置情報の取得を開始する。設定項目が大量にあり、iOS 専用・Android 専用なオプションもあったりするので、公式の API ドキュメントを参照されたし。

configure() のコールバック関数の中で、状態 state を確認できる。もしも位置情報の取得が始まっていないようであれば start() を叩くようにする。

この処理を deviceready イベントの中で発火させれば、アプリ起動時から位置情報の取得・送信を開始できるし、任意のボタンを押した時のイベントに追加しても良い。

これで位置情報を取得し始めることができた。バックグラウンドでも位置情報を取得してくれる。

iOS シミュレータでの位置情報設定

iOS 実機の場合は実際に動作検証がしやすいが、iOS シミュレータの場合も、位置情報サービスをシミュレートすることができるので、このプラグインの動作確認ができる。

iOS シミュレータを起動したら、メニューより Debug → Location と進む。項目の内容は以下のとおり。

項目名 内容
None 位置指定なし
Custom Location 任意の座標を指定する
Apple アップルの本社に立ち止まる
City Bicycle Ride 自転車移動を再現したスピードで移動する
City Run ジョギングを再現したスピードで移動する
Freeway Drive 車移動を再現したスピードで移動する

試しに Freeway Drive でも選ぶと、locationhttp イベントによるログが頻繁に出力されることが Safari インスペクタなどから確認できるであろう。また、Apple など停止状態のものを選ぶと、heartbeat が効いているかも確認できる。

位置情報の取得処理を停止する

このままだとアプリをアプリスイッチャーから完全に終了させるまで位置情報の取得を続けてしまうので、停止ボタンを作ろう。停止するための処理は以下のとおり。

// 現在のプラグインの動作状況を確認する
window.BackgroundGeolocation.getState((state) => {
  // 既に停止していれば何もしない
  if(!state.enabled) {
    return;
  }
  
  // 動作中であれば、位置情報の取得を停止する
  window.BackgroundGeolocation.stop(() => {
    // on() で設定したイベント類を全て削除する
    window.BackgroundGeolocation.removeListeners(() => {
      console.log('停止完了');
    });
  });
});

コールバック関数の入れ子が3つになるので、適宜 Promise 化したヘルパーメソッドを作っても良いだろう。

メインは stop() さえ呼べれば良いワケだが、removeListeners() により、on() で登録していた各種イベントを削除しておいたので、また上述の開始処理をまるごと呼び出してもイベントが重複しないようになっている。

サーバ側に送られる情報は?

一方、位置情報を受け取るサーバ側は、どのように情報が受け取れるだろうか。プラグインが POST 送信する内容は、以下のような内容になっている。

{
  "location": {
    "coords": {
      "speed": 7.5,
      "longitude": -120.327218,
      "latitude": 35.33500926,
      "accuracy": 5,
      "altitude_accuracy": -1,
      "altitude": 0,
      "heading": 245.02
    },
    "extras": {},
    "is_moving": false,
    "event": "motionchange",
    "odometer": 0,
    "uuid": "59881E61-4CB8-4028-A557-151089948542",
    "activity": {
      "type": "unknown",
      "confidence": 100
    },
    "battery": {
      "level": -1,
      "is_charging": false
    },
    "timestamp": "2017-01-01T01:02:03.456Z"
  },
  "some-token": "任意のトークンを付けるとか"
  "id": 1
}

iOS シミュレータによる位置偽装を使い、JSON-Server で受信したデータがこんな感じ。some-tokenconfigure() 内で params を指定した時の内容なので、何も設定していなければ何も付かない。それ以外はプラグインが付与する内容。

locationcoords の内容を見ると、以前紹介した Geolocation API の形式に則っていることが分かる。ココだけ抽出すれば、緯度・経度や移動速度が分かるであろう。その他、is_movingevent などが coords の外に付与されているし、uuidbattery など端末情報も確認できる。


プラグインを入れるだけで位置情報を継続的に取得できるのでオススメ。