位置情報を最適化してバッテリーの消耗を抑える

Android 8.0(API レベル 26)で導入されたバックグラウンドでの位置情報の制限により、位置情報サービスの使用によるバッテリーの消耗への影響が改めて注目されるようになりました。このページでは、位置情報サービスに関するおすすめの方法を紹介し、アプリの電池消費を効率化するために今すぐできることについて説明します。ここで説明するおすすめの方法を採用すると、アプリが実行されているプラットフォームのバージョンに関係なく、アプリのパフォーマンスを改善できます。

Android 8.0 のバックグラウンド位置情報の制限に伴い、以下の変更が導入されました。

  • バックグラウンドでの位置情報の収集はスロットリングされ、位置情報の計算と配信は 1 時間あたり数回のみになります。
  • Wi-Fi スキャンは頻度が減少し、デバイスが同一の静的アクセス ポイントへの接続を維持しているときは位置情報の更新データが計算されません。
  • ジオフェンスの応答性が数十秒から約 2 分に変更されました。この変更により、電池のパフォーマンスが著しく向上し、デバイスによっては最大 10 倍になります。

このページの説明は Google 位置情報サービス API の使用を前提としています。これらの API はフレームワーク位置情報 API よりも精度が高く、電池の消費量を軽減する効果があります。このページでは、特に Fused Location Provider API に精通していることを前提としています。この API は、GPS、Wi-Fi、セルラー ネットワークからのシグナルだけでなく、加速度計、ジャイロスコープ、磁力計などのセンサーからのシグナルも組み合わせて使用します。さらに、Fused Location Provider API を基盤に構築され、電池のパフォーマンスの面で最適化された Geofencing API にも精通している必要があります。

電池の消耗について

位置情報の収集と電池の消耗は、以下と直接的な関係があります。

  • 精度: 位置情報データの精度。一般的に、精度が高いほど電池の消耗が激しくなります。
  • 頻度: 位置情報が計算される頻度。位置情報の計算頻度が高いほど、電池の消費量が増加します。
  • レイテンシ: 位置情報データの配信が遅れる度合。一般的に、レイテンシが低いほど、電池の消費量が増加します。

精度

位置情報の精度を指定するには、setPriority() メソッドを使用し、次の値のいずれかを引数として渡します。

  • PRIORITY_HIGH_ACCURACY では、可能な限り最も高い精度の位置情報が提供されます。必要な数の入力を使用して位置情報が計算され(GPS、Wi-Fi、セルラーが有効化され、さまざまなセンサーが使用されます)、電池が著しく消耗する可能性があります。
  • PRIORITY_BALANCED_POWER_ACCURACY では、正確な位置情報が提供される一方で、電力消費が最適化されます。GPS はほとんど使用されません。通常は、Wi-Fi 情報とセルラー情報の組み合わせがデバイスの位置情報の計算に使用されます。
  • PRIORITY_LOW_POWER では、主として基地局の情報に依存し、GPS と Wi-Fi からの入力の使用を避けて、電池の消耗を最小限に抑えつつ、低い精度(都市レベル)の位置情報を提供します。
  • PRIORITY_NO_POWER では、位置情報がすでに計算されている他のアプリからパッシブに位置情報を受け取ります。

ほとんどのアプリでは、バランスのとれた電力オプションまたは低電力オプションを使用して、必要な位置情報を取得できます。高精度のオプションは、フォアグラウンドで実行され、リアルタイムの位置情報の更新を必要とするアプリ(地図アプリなど)でのみ使用してください。

頻度

位置情報の頻度は、以下の 2 つの方法で指定できます。

  • setinterval() メソッドを使用して、アプリのために位置情報を計算する時間間隔を指定します。
  • setFastestInterval() を使用して、他のアプリのために計算された位置情報をアプリに配信する時間間隔を指定します。

setInterval() を使用する場合は、できるだけ大きい値を渡してください。これは、好ましくない電池の消耗の原因になりがちなバックグラウンドの位置情報収集では、特に重要です。フォアグラウンドのユースケースに限り、間隔を数秒に設定してください。このような戦略は Android 8.0 で導入されたバックグラウンド位置情報の制限に伴って必須になりましたが、Android 7.0 以前のデバイスでもアプリに適用するように努める必要があります。

レイテンシ

レイテンシを指定するには、setMaxWaitTime() メソッドを使用します。通常は、setInterval() メソッドで指定した間隔より数倍大きい値を渡します。この設定により、位置情報の配信が遅延されます。また、複数の位置情報の更新データがバッチで配信される場合があります。この 2 つの変更により、電池の消費を最小限に抑えることができます。

アプリが位置情報の更新データをすぐに必要としない場合は、setMaxWaitTime() メソッドにできるだけ大きい値を渡します。これにより、レイテンシの増加と引き換えにデータ処理量が増え、電池の消費が効率化されます。

アプリがジオフェンスを使用する場合は、電力を節約するため、setNotificationResponsiveness() メソッドに大きい値を渡す必要があります。5 分以上の値を設定することをおすすめします。

位置情報のユースケース

このセクションでは、一般的な位置情報収集のシナリオについて説明し、Geofencing API と Fused Location Provider API の最適な使用方法に関する推奨事項を紹介します。

ユーザーから見える(フォアグラウンドの)更新

例: 非常に低いレイテンシと高頻度の正確な更新を必要とする地図アプリ。すべての更新はフォアグラウンドで行われます。ユーザーはアクティビティを開始し、位置情報データを使用して、その後間もなくアクティビティを停止します。

PRIORITY_HIGH_ACCURACY または PRIORITY_BALANCED_POWER_ACCURACY の値を指定して、setPriority() メソッドを使用します。

setInterval() メソッドに指定する間隔は、ユースケースに応じて異なります。リアルタイムの更新が必要な状況では、値を数秒に設定します。それ以外の場合は、数分に制限します(電池使用量を最小限に抑えるため、おおよそ 2 分以上が推奨されます)。

デバイスの位置を確認する

例: デバイスの位置情報の確認を求める天気予報アプリ。

getLastLocation() メソッドを使用します。このメソッドは、最新の利用可能な位置情報(まれに Null の場合があります)を返します。このメソッドを使用すると、位置情報を簡単に取得できます。また、位置情報の更新データをアクティブにリクエストすることに伴うコストが発生しません。getLastLocation() から返される位置情報が十分新しい場合に true を返す isLocationAvailable() メソッドと組み合わせて使用してください。

ユーザーが特定の場所にいるときに更新を開始する

例: ユーザーが職場、自宅、その他の場所から特定の距離内にいるときに更新データをリクエストする。

ジオフェンスを、融合された位置予測プロバイダの更新データと組み合わせて使用します。アプリがジオフェンスへの entrance トリガーを受信したら更新データをリクエストし、ジオフェンスからの exit トリガーを受信したら更新データを削除します。これにより、定義されたエリア内にユーザーが入ったときにのみ、アプリはより精度の高い位置情報の更新データを取得します。

このシナリオの一般的なワークフローでは、ユーザーがジオフェンス内に入ったときに通知を表示し、ユーザーが通知をタップしたときに更新データをリクエストするコードを含むアクティビティを起動します。

ユーザーのアクティビティの状態に基づいて更新を開始する

例: ユーザーがバイクを運転しているとき、またはバイクにまたがっているときにのみ更新データをリクエストする。

Activity Recognition API を、融合された位置予測プロバイダの更新データと組み合わせて使用します。対象となるアクティビティが検出された時点で更新データをリクエストし、ユーザーがそのアクティビティの実行を停止した時点で更新データを削除します。

このユースケースの一般的なワークフローでは、検出されたアクティビティの通知を表示し、ユーザーが通知をタップしたときに更新データをリクエストするコードを含むアクティビティを起動します。

地理的エリアに関連付けられたバックグラウンド位置情報の更新を長期的に実行する

例: デバイスが特定の店舗に近づいたときに通知を受けることをユーザーが求めている。

これはジオフェンスに最適なユースケースです。このユースケースでは、ほぼ確実にバックグラウンド位置情報が必要になるため、addGeofences(GeofencingRequest, PendingIntent) メソッドを使用します。

以下の構成オプションを設定する必要があります。

  • 滞留状態の遷移をトラッキングする場合は、setLoiteringDelay() メソッドを使用し、おおよそ 5 分以下の値を渡します。

  • setNotificationResponsiveness() を使用し、おおよそ 5 分の値を渡します。ただし、アプリが応答性で追加の遅延を管理できる場合は、おおよそ 10 分の値を使用することを検討してください。

アプリが一度に登録できるジオフェンスの数は最大 100 個に制限されています。アプリが多数の店舗オプションをトラッキングすることを要求するユースケースでは、大きいジオフェンス(都市レベル)を登録し、大きいジオフェンス内の店舗に対して小さいジオフェンス(都市内の区域)を動的に登録することもできます。ユーザーが大きいジオフェンス内に入ったら、小さいジオフェンスを追加します。ユーザーが大きいジオフェンスから出たら、小さいジオフェンスを削除して、新しいエリアのジオフェンスを再登録します。

アプリ コンポーネントが見えない状態でバックグラウンド位置情報の更新を長期的に実行する

例: 位置情報をパッシブにトラッキングするアプリ。

可能であれば、ほとんど電池を消耗しない PRIORITY_NO_POWER オプションを指定して setPriority() メソッドを使用します。PRIORITY_NO_POWER を使用できない場合は、PRIORITY_BALANCED_POWER_ACCURACY または PRIORITY_LOW_POWER を使用します。ただし、継続的なバックグラウンド処理には PRIORITY_HIGH_ACCURACY を使用しないでください。このオプションを使用すると電池が著しく消耗します。

より多くの位置情報データが必要な場合は、パッシブな位置情報を利用します。そのためには、setFastestInterval() メソッドを呼び出し、setInterval() に渡す値よりも小さい値を渡します。PRIORITY_NO_POWER オプションと組み合わせると、パッシブな位置情報を利用して、他のアプリが計算した位置情報を追加のコストなしで配信できます。

setMaxWaitTime() メソッドにより、レイテンシを追加して頻度を適切に調整します。たとえば、おおよそ 10 分の値を指定して setinterval() メソッドを使用する場合は、30 分~60 分の値を指定して setMaxWaitTime() を呼び出すことを検討してください。これらのオプションを使用すると、アプリ用の位置情報が約 10 分間隔で計算されますが、アプリは 30 分~60 分の間隔でウェイクアップし、バッチ更新データとして取得できる位置情報を使用します。このアプローチでは、レイテンシの増加と引き換えに、処理できるデータ量が増え、電池のパフォーマンスが向上します。

ユーザーが他のアプリを操作している間に高精度の更新を頻繁に行う

例: ユーザーが画面をオフにするか別のアプリを開いても引き続き動作するナビゲーション アプリやフィットネス アプリ。

フォアグラウンド サービスを使用します。アプリがユーザーに代わって負荷の高い処理を行う可能性がある場合は、その処理をユーザーに意識させることをおすすめします。フォアグラウンド サービスは、継続的な通知を必要とします。詳細については、通知の概要をご覧ください。

位置情報に関するおすすめの方法

このセクションで紹介するおすすめの方法を実装すると、アプリの電池使用量の削減に役立ちます。

位置情報の更新データを削除する

必要のない電池消費の一般的な原因として、不要になった位置情報の更新データの削除に失敗するケースがあります。この問題が発生するのは、たとえば、アクティビティの onStart() または onResume() ライフサイクル メソッドに requestlocationUpdates() の呼び出しが含まれているが、対応する removeLocationUpdates() の呼び出しが onPause() または onStop() ライフサイクル メソッドに含まれていない場合です。

ライフサイクル対応コンポーネントを使用すると、アプリ内のアクティビティのライフサイクルをより適切に管理できます。詳細については、ライフサイクル対応コンポーネントによるライフサイクルへの対応をご覧ください。

タイムアウトを設定する

電池の消耗を防ぐには、位置情報の更新を停止すべき時点に適切なタイムアウトを設定します。タイムアウトにより、更新が果てしなく続くことがなくなり、(コードのバグなどが原因で)リクエストされた更新データが削除されなかった場合にアプリが保護されます。

融合された位置予測プロバイダのリクエストの場合は、setExpirationDuration() を呼び出してタイムアウトを追加します。このメソッドは、メソッドが最後に呼び出された時点からの経過時間をミリ秒単位で表すパラメータを受け取ります。タイムアウトを追加するには、setExpirationTime() を呼び出す方法もあります。このメソッドは、システムが最後に起動された時点からの有効期限をミリ秒単位で表すパラメータを受け取ります。

ジオフェンスの位置情報リクエストにタイムアウトを追加するには、setExpirationDuration() メソッドを呼び出します。

バッチ リクエスト

フォアグラウンドのユースケースを除くすべてのユースケースでは、複数のリクエストをまとめてバッチで処理します。setInterval() メソッドを使用して、位置情報を計算する間隔を指定できます。次に、setMaxWaitTime() メソッドを使用して、位置情報をアプリに配信する間隔を設定します。setMaxWaitTime() メソッドに渡す値は、setInterval() メソッドに渡す値の倍数でなければなりません。たとえば、次の位置情報リクエストを見てみましょう。

Kotlin

val request = LocationRequest()
request.setInterval(10 * 60 * 1000)
request.setMaxWaitTime(60 * 60 * 1000)

Java

LocationRequest request = new LocationRequest();
request.setInterval(10 * 60 * 1000);
request.setMaxWaitTime(60 * 60 * 1000);

この場合、位置情報は約 10 分ごとに計算され、おおよそ 6 個の位置情報データポイントが約 1 時間ごとにバッチで配信されます。位置情報の更新データは約 10 分ごとに取得されますが、デバイスは約 1 時間ごとにウェイクアップするので、電池を節約できます。

パッシブな位置情報の更新を利用する

バックグラウンドのユースケースでは、位置情報の更新をスロットリングすることをおすすめします。このおすすめの方法は Android 8.0 の制限に伴って必須になりましたが、それより古いデバイスで実行されるアプリでも、可能な限りバックグラウンド位置情報を制限するように努める必要があります。

自分のアプリがバックグラウンドで実行されているときに、別のアプリがフォアグラウンドで頻繁に位置情報の更新データをリクエストする場合があります。位置情報サービスにより、この更新データを自分のアプリで使用できます。他のアプリの位置情報データを利用する以下の位置情報リクエストの使用を検討してください。

Kotlin

val request = LocationRequest()
request.setInterval(15 * 60 * 1000)
request.setFastestInterval(2 * 60 * 1000)

Java

LocationRequest request = new LocationRequest();
request.setInterval(15 * 60 * 1000);
request.setFastestInterval(2 * 60 * 1000);

前の例では、アプリ用の位置情報が約 15 分ごとに計算されていました。他のアプリが位置情報をリクエストする場合、そのデータを最大 2 分間隔で自分のアプリに利用できます。

パッシブな位置情報を利用する場合、電池の消耗は発生しませんが、位置情報データの受信によって負荷の高い CPU オペレーションまたは I/O オペレーションが発生するケースがあるため、その点にも注意する必要があります。電池のコストを最小限に抑えるには、setFastestInterval() に短すぎる間隔を指定しないようにします。

このページに記載された推奨事項に従うと、ユーザーのデバイスの電池のパフォーマンスを大幅に改善できます。電池を消耗しないアプリは、ユーザーによって削除される可能性が低くなります。