Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

位置情報に関する戦略

注: このガイドで説明している戦略は、android.location 内のプラットフォーム位置情報 API に適用されます。Google Play 開発者サービスの一部である Google Location Services API の方が、強力なハイレベルのフレームワークを備えており、位置情報提プロバイダや、ユーザーの移動、位置情報の精度を自動的に処理することができます。また、ユーザーが指定した消費電力パラメータに基づいて、位置情報の更新スケジュールを設定します。ほとんどの場合、Location Services API を使用した方が、バッテリー性能や位置情報の精度を高めることができます。

Location Services API の詳細については、Android 向け Google Location Services をご覧ください。

ユーザーの現在地を把握することで、アプリの性能を高め、ユーザーに精度の高い情報を提供できるようになります。Android 向けの位置認識アプリを開発する場合、GPS と Android の Network Location Provider を利用してユーザーの位置情報を取得できます。GPS は極めて正確ですが、屋外でしか使用できず、電池の消耗が速いうえ、ユーザーが期待するほどすばやく位置情報を取得できません。Android の Network Location Provider は、携帯電話の基地局と Wi-Fi シグナルを利用してユーザーの現在地を特定します。この方法は、電池の消耗が少なく、屋内でも屋外でも機能し、迅速に位置情報を取得できます。アプリでユーザーの位置情報を取得するには、GPS と Network Location Provider の両方あるいはどちらかを使用します。

ユーザーの現在地を特定する際の課題

モバイル デバイスでユーザーの位置情報を取得する処理は複雑になることがあります。位置情報源に関係なく、さまざまな理由により、取得した位置情報にエラーが含まれていたり、情報が正確でなかったりすることがあります。ユーザーの位置情報に関するエラーの原因には以下のようなものがあります。

  • 多数の位置情報源

    GPS やセル ID、Wi-Fi は、それぞれにユーザーの位置情報に関する手掛かりを提供します。どれを使用し、信頼するかは、精度、スピード、バッテリー効率に関するトレードオフに基づいて判断します。

  • ユーザーの移動

    ユーザーの位置情報は変化するため、ユーザーの位置情報を定期的に再推定することによって、ユーザーの移動を把握する必要があります。

  • 精度の変化

    各位置情報源から取得できる位置情報の推定値は、精度の面で一貫していません。ある情報源から 10 秒前に取得した位置情報は、別の情報源または同じ情報源から取得した最新の位置情報よりも正確な場合があります。

このような問題により、信頼できるユーザー位置情報を取得することが難しい場合があります。このドキュメントでは、こうした課題に対応し、信頼できる位置情報を取得するうえで役に立つ情報を提供します。また、位置情報を正確かつ迅速にユーザーに提供するためにアプリで使用できるアイデアを紹介します。

現在地の更新情報をリクエストする

上記の位置情報エラーに対処する前に、Android でユーザーの位置情報を取得する方法について説明します。

Android 内でユーザーの位置情報を取得するには、コールバックを使用します。LocationManager から位置情報の更新を受け取るリクエストを行うには、requestLocationUpdates() を呼び出して、LocationListener に渡します。LocationListener は、ユーザーの位置情報が変化したときや、サービスのステータスが変化したときに LocationManager が呼び出すコールバック メソッドを実装する必要があります。

注: Android 8.0(API レベル 26)以降の場合、バックグラウンドで実行中のアプリが現在地情報をリクエストしても、デバイスが位置情報を計算するのは 1 時間に数回だけに限られます。このような位置情報計算の制限に対してアプリを最適化する方法については、バックグラウンド位置情報の制限をご覧ください。

LocationListener を定義して位置情報の更新をリクエストするコードを以下に示します。

Kotlin

    // Acquire a reference to the system Location Manager
    val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager

    // Define a listener that responds to location updates
    val locationListener = object : LocationListener {

        override fun onLocationChanged(location: Location) {
            // Called when a new location is found by the network location provider.
            makeUseOfNewLocation(location)
        }

        override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {
        }

        override fun onProviderEnabled(provider: String) {
        }

        override fun onProviderDisabled(provider: String) {
        }
    }

    // Register the listener with the Location Manager to receive location updates
    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0f,locationListener)
    

Java

    // Acquire a reference to the system Location Manager
    LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

    // Define a listener that responds to location updates
    LocationListener locationListener = new LocationListener() {
        public void onLocationChanged(Location location) {
          // Called when a new location is found by the network location provider.
          makeUseOfNewLocation(location);
        }

        public void onStatusChanged(String provider, int status, Bundle extras) {}

        public void onProviderEnabled(String provider) {}

        public void onProviderDisabled(String provider) {}
      };

    // Register the listener with the Location Manager to receive location updates
    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
    

requestLocationUpdates() 内の最初のパラメータで、使用する位置情報プロバイダのタイプを指定します(上記のコードの場合、基地局と Wi-Fi を利用して位置情報を取得する Network Location Provider が指定されています)。2 つ目と 3 つ目のパラメータで、リスナーが更新情報を受け取る頻度を制御できます(2 つ目のパラメータは通知間の最小間隔、3 つ目のパラメータは通知間の距離の最小変化を指定します)。両方のパラメータをゼロに設定すると、可能な限り頻繁に位置情報の通知をリクエストすることができます。最後のパラメータで、位置情報の更新に関するコールバックを受け取る LocationListener を指定します。

GPS プロバイダに位置情報の更新をリクエストする場合は、NETWORK_PROVIDER の代わりに GPS_PROVIDER を使用します。GPS プロバイダと Network Location Provider の両方に位置情報の更新をリクエストすることもできます。その場合は、requestLocationUpdates() を 2 回呼び出します(NETWORK_PROVIDER 向けに 1 回、GPS_PROVIDER 向けに 1 回)。

ユーザー パーミッションをリクエストする

NETWORK_PROVIDER または GPS_PROVIDER から位置情報の更新を受け取るには、Android マニフェスト ファイル内で ACCESS_COARSE_LOCATION パーミッションまたは ACCESS_FINE_LOCATION パーミッションをそれぞれ宣言して、ユーザー パーミッションをリクエストする必要があります。パーミッションを宣言していない場合、位置情報の更新をリクエストすると、実行時にアプリにエラーが発生します。

NETWORK_PROVIDERGPS_PROVIDER の両方を使用する場合は、ACCESS_FINE_LOCATION パーミッションだけをリクエストすれば済みます(こちらのパーミッションには、両方のプロバイダに対するパーミッションが含まれています)。ACCESS_COARSE_LOCATION パーミッションの場合、NETWORK_PROVIDER へのアクセスだけが許可されます。

注: Android 5.0(API レベル 21)以降をターゲットとしているアプリの場合、位置情報の更新を NETWORK_PROVIDER から受信するか、GPS_PROVIDER から受信するかに応じて、android.hardware.location.network ハードウェア機能または android.hardware.location.gps ハードウェア機能をアプリが使用することをマニフェスト ファイル内で宣言する必要があります。アプリが上記のいずれかの位置情報プロバイダから位置情報を受け取る場合、アプリ マニフェスト内で、対象のハードウェア機能をアプリが使用することを宣言する必要があります。Android 5.0(API 21)より前のバージョンを搭載しているデバイスの場合、ACCESS_FINE_LOCATION パーミッションや ACCESS_COARSE_LOCATION パーミッションのリクエストには、位置情報ハードウェア機能を求める暗黙的なリクエストが含まれます。しかし、Android 5.0(API レベル 21)以降の場合、上記のパーミッションをリクエストしても、位置情報ハードウェア機能が自動的にリクエストされることはありません。

デバイスの GPS からデータを読み取るアプリのマニフェスト ファイル内で、パーミッションとハードウェア機能を宣言するサンプルコードを以下に示します。

    <manifest ... >
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        ...
        <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
        <uses-feature android:name="android.hardware.location.gps" />
        ...
    </manifest>
    

最高のパフォーマンスを引き出すモデルを定義する

位置情報を使用するアプリは今では珍しくありませんが、精度の不足、ユーザーの移動、位置情報を取得するためのさまざまな手法、電池の節約に対する要望により、ユーザーの位置情報の取得方法が複雑になっています。電池の消耗を抑えつつ、正確なユーザーの位置情報の取得を妨げる障害を克服するには、アプリによる位置情報の取得方法を指定する一貫したモデルを定義する必要があります。このモデルには、更新情報のリスニングを開始および停止するタイミングと、キャッシュされている位置情報を使用するタイミングを含めます。

ユーザーの位置情報の取得フロー

ユーザーの位置情報を取得する手順の一般的なフローを以下に示します。

  1. アプリを起動します。
  2. しばらくしてから、目的の位置情報提供元からの更新情報のリスニングを開始します。
  3. 新しいが精度の低い位置情報を除外して、現在地の「最良推定値」を保守します。
  4. 現在地の更新情報のリスニングを停止します。
  5. 最新の位置情報の最良推定値を利用します。

このモデルのタイムラインを図 1 に示します。この図は、アプリが位置情報の更新をリッスンする期間と、その期間中に発生するイベントを視覚的に表示しています。

図 1: アプリが位置情報の更新をリッスンする期間を示すタイムライン

このタイムライン(現在地の更新情報を受け取る期間)のモデルは、位置情報を利用したサービスをアプリに追加する場合に行う必要があるさまざまな意思決定で構成されています。

更新情報のリスニングを開始するタイミングを決定する

アプリが起動したらすぐに、またはユーザーが特定の機能を有効にした後にのみ、現在地の更新情報のリスニングを開始することをおすすめします。位置情報の更新をリッスンする期間が長いと、バッテリーを大量に消費する可能性があります。他方、リッスンする期間が短いと、十分な精度が得られない可能性があります。

上記で示したように、更新のリッスンを開始するには、requestLocationUpdates() を呼び出します。

Kotlin

    val locationProvider: String = LocationManager.NETWORK_PROVIDER
    // Or, use GPS location data:
    // val locationProvider: String = LocationManager.GPS_PROVIDER;

    locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener)
    

Java

    String locationProvider = LocationManager.NETWORK_PROVIDER;
    // Or, use GPS location data:
    // String locationProvider = LocationManager.GPS_PROVIDER;

    locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
    

最後の既知の位置情報を利用してすばやく位置情報を更新する

位置情報リスナーが最初に位置情報の更新を受け取るまでにあまりに時間がかかり、ユーザーのフラストレーションを引き起こすことがよくあります。位置情報リスナーに正確な位置情報が提供されるまでは、getLastKnownLocation(String) を呼び出すことで、キャッシュ済みの位置情報を活用することをおすすめします。

Kotlin

    val locationProvider: String = LocationManager.NETWORK_PROVIDER
    // Or use LocationManager.GPS_PROVIDER

    val lastKnownLocation: Location = locationManager.getLastKnownLocation(locationProvider)
    

Java

    String locationProvider = LocationManager.NETWORK_PROVIDER;
    // Or use LocationManager.GPS_PROVIDER

    Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);
    

更新情報のリッスンを停止するタイミングを決定する

新しい位置情報が不要になるタイミングを決定するロジックは、アプリに応じて非常に単純なものから非常に複雑なものまで多岐にわたります。位置情報を取得してから使用するまでの時間が短いと、推定値の精度が向上します。リッスンする時間が長くなると、バッテリーが大量に消費されます。そのため、必要な情報を取得したらすぐに removeUpdates(PendingIntent) を呼び出して、更新のリッスンを停止してください。

Kotlin

    // Remove the listener you previously added
    locationManager.removeUpdates(locationListener)
    

Java

    // Remove the listener you previously added
    locationManager.removeUpdates(locationListener);
    

現在の最良推定値を維持する

最新の位置情報が最も正確であると思うかもしれません。しかし、位置情報の精度は変化するため、最新の位置情報が必ずしも常に最も正確であるとは限りません。そのため、一定の基準に基づいて位置情報の更新を選択できるロジックを組み込む必要があります。この基準は、アプリやフィールド テストのユースケースによって異なります。

位置情報の精度を検証する際は、以下のような手順を行います。

  • 取得した位置情報が、以前の推定値と比べて大幅に新しいかどうかをチェックします。
  • 位置情報の精度が以前の推定値より高いか低いかを確認します。
  • 新しい位置情報のプロバイダをチェックして、以前よりも信頼性が高いかどうかを判断します。

このロジックの詳細な例を以下に示します。

Kotlin

    private const val TWO_MINUTES: Long = 1000 * 60 * 2

    /** Determines whether one Location reading is better than the current Location fix
     * @param location The new Location that you want to evaluate
     * @param currentBestLocation The current Location fix, to which you want to compare the new one
     */
    fun isBetterLocation(location: Location, currentBestLocation: Location?): Boolean {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true
        }

        // Check whether the new location fix is newer or older
        val timeDelta: Long = location.time - currentBestLocation.time
        val isSignificantlyNewer: Boolean = timeDelta > TWO_MINUTES
        val isSignificantlyOlder:Boolean = timeDelta < -TWO_MINUTES

        when {
            // If it's been more than two minutes since the current location, use the new location
            // because the user has likely moved
            isSignificantlyNewer -> return true
            // If the new location is more than two minutes older, it must be worse
            isSignificantlyOlder -> return false
        }

        // Check whether the new location fix is more or less accurate
        val isNewer: Boolean = timeDelta > 0L
        val accuracyDelta: Float = location.accuracy - currentBestLocation.accuracy
        val isLessAccurate: Boolean = accuracyDelta > 0f
        val isMoreAccurate: Boolean = accuracyDelta < 0f
        val isSignificantlyLessAccurate: Boolean = accuracyDelta > 200f

        // Check if the old and new location are from the same provider
        val isFromSameProvider: Boolean = location.provider == currentBestLocation.provider

        // Determine location quality using a combination of timeliness and accuracy
        return when {
            isMoreAccurate -> true
            isNewer && !isLessAccurate -> true
            isNewer && !isSignificantlyLessAccurate && isFromSameProvider -> true
            else -> false
        }
    }
    

Java

    private static final int TWO_MINUTES = 1000 * 60 * 2;

    /** Determines whether one Location reading is better than the current Location fix
      * @param location  The new Location that you want to evaluate
      * @param currentBestLocation  The current Location fix, to which you want to compare the new one
      */
    protected boolean isBetterLocation(Location location, Location currentBestLocation) {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true;
        }

        // Check whether the new location fix is newer or older
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            return true;
        // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Check whether the new location fix is more or less accurate
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = isSameProvider(location.getProvider(),
                currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    /** Checks whether two providers are the same */
    private boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
          return provider2 == null;
        }
        return provider1.equals(provider2);
    }
    

バッテリー消費やデータ交換を減らすようにモデルを調整する

アプリをテストするとき、正確な位置情報を提供し、優れたパフォーマンスを実現するために、モデルの調整が必要であることに気付くかもしれません。この 2 点をうまく両立させるために、以下の変更が必要になることがあります。

リスニング期間を短縮する

現在地の更新情報をリッスンする期間が短いと、GPS およびネットワーク位置情報サービスとのやり取りが少なくなるため、電池を長持ちさせることができます。ただし、位置情報の最良推定値の選択肢も少なくなります。

更新情報を返す頻度を減らすように位置情報提供元を設定する

リスニング期間中の新しい位置情報の出現率を減らすことで、精度は犠牲になりますが、電池の効率を高めることができます。このトレードオフのどちらを重視するかは、アプリの用途によって異なります。更新間隔や距離の最小変化を指定する requestLocationUpdates() 内のパラメータを増加することで、更新頻度を抑えることができます。

プロバイダを制限する

アプリの使用環境や要求される精度のレベルによっては、Network Location Provider と GPS の両方ではなく、どちらか 1 つのみを使用するように選択できます。1 つのサービスだけを利用することで、精度が犠牲になる可能性がありますが、電池使用量を削減できます。

一般的なアプリのユースケース

アプリでユーザーの位置情報を取得する理由はさまざまです。以下に、ユーザーの位置情報を使用することによってアプリの価値を高めることができるシナリオをいくつか紹介します。各シナリオでは、位置情報の精度を高めること、電池を長持ちさせることを目的として、位置情報のリスニングを開始および停止するタイミングに関するおすすめの方法についても説明します。

位置情報を使用してユーザー作成コンテンツをタグ付けする

位置情報を使用してユーザー作成コンテンツをタグ付けするアプリを作成する場合があります。ユーザーは現在地情報を利用することで、たとえば、地元のスポットの感想をシェアしたり、レストランのレビューを投稿したり、拡張現実コンテンツを録画したりすることができます。位置情報サービスに関して、このようなインタラクションがどのように行われるのかを示すモデルを図 2 に示します。

図 2: アプリがユーザーの位置情報を取得した後、ユーザーが現在地情報を利用してリッスンが停止するまでの期間を示すタイムライン

この図は、アプリでユーザーの位置情報を取得する方法を示した以前のモデル(図 1)と一致しています。位置情報の精度を最大限に高めるために、ユーザーがコンテンツの作成を開始したときだけでなく、アプリが起動したときにも、現在地の更新情報のリスニングを開始できます。さらに、コンテンツを投稿または記録する準備ができたときに更新情報のリスニングを停止します。場合によっては、コンテンツを作成する通常の作業にかかる時間を検討し、その時間内で位置情報の推定値を効率的に収集できるかどうかを判断する必要があります。

ユーザーに行き先の選択肢を提示する

ユーザーに行き先の選択肢を提示するアプリを作成する場合があります。たとえば、周辺にあるレストランや店舗、娯楽施設のリストを表示し、ユーザーの位置情報に応じておすすめの順序を変更できるアプリなどが該当します。

このようなフローに対応するには、たとえば、次のように設定します。

  • 新しい最良推定値を取得したら、おすすめを並べ替える
  • おすすめの順序が確定したら、更新情報のリッスンを停止する

このようなモデルの視覚的なタイムラインを図 3 に示します。

位置情報データの改善を反復するイベントのタイムライン

図 3: ユーザーの位置情報が更新されるたびにデータセットを動的に更新する期間を示すタイムライン

仮の位置情報データを提供する

アプリを開発する際は、ユーザーの位置情報を取得するためのモデルがどの程度適切に機能するかを必ずテストする必要があります。このテストは、実際の Android デバイスを使用することで極めて簡単に実施できます。デバイスがない場合でも、Android Emulator 内で仮の位置情報を提供することで、位置情報ベースの機能をテストすることができます。仮の位置情報データをアプリに送信するには、デバイスの開発者向けオプション内に用意されている仮の位置情報用のオプションを使用するか、エミュレータ コンソール内で geo コマンドを使用します。

注: 仮の位置情報は GPS の位置情報として挿入されるため、仮の位置情報データを機能させるには、GPS_PROVIDER に位置情報の更新をリクエストする必要があります。

開発者向けオプションを使用する

デバイス上で、開発者向けオプションと USB デバッグを有効にして、[仮の現在地情報アプリを選択] を使用する手順に沿って設定します。

エミュレータ コンソール内で geo コマンドを使用する

コマンドラインから仮の位置情報データを送信する手順は次のとおりです。

  1. Android Emulator 内でアプリを起動して、SDK の /tools ディレクトリにあるターミナル / コンソールを開きます。
  2. エミュレータ コンソールに接続します。
    telnet localhost <console-port>
  3. 位置情報データを送信します。
    • 固定位置情報を送信する場合は、geo fix を使用します。

      このコマンドの場合、経度および緯度(10 進数)と、必要に応じて高度(メートル単位)を指定できます。たとえば、次のようになります。

      geo fix -121.45356 46.51119 4392
    • NMEA 0183 センテンスを送信する場合は、geo nmea を使用します。

      このコマンドの場合、「$GPGGA」タイプ(固定データ)または「$GPRMC」タイプ(遷移データ)の単一の NMEA センテンスを指定できます。たとえば、次のようになります。

      geo nmea $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62

エミュレータ コンソールに接続する方法については、エミュレータ コンソールを使用するをご覧ください。