Android Location APIで位置情報(緯度と経度)を取得する ※※LocationManager非推奨※※


前書き

Androidアプリで位置情報を取得しようと調査していたら現在は非推奨になっているサンプルが多数ありました。LocationManagerなどは非推奨になっています。deprecatedではないようですね。推奨されている方法を調査してまとめてみることにしました。

構成

  1. Kotlinで書きます。
    非推奨のサンプルは時が経っていることを表し故にJavaで書かれています。Kotlinで書きます。
  2. 現在地を取得します。
    既に公開されているサンプルコードに敬意を払いつつもAndroid 6以降の実行時のパーミッション リクエストと同時にコードが記載されているので複雑に感じました。まず目的である現在地を取得するサンプルを書きます。 目的の処理に先に辿り着くことで挫折を防ぎます
  3. 実行時のパーミッション リクエストは考慮しません。
    パーミッションは結局必要なものですが一時的に現実逃避します
    一度にたくさんの状況を把握することが苦手な筆者が理解して記事を投稿出来る(説明出来る)ようにしますのでパーミッションの処理は書きません。

動作環境

 
macOS High Sierra 10.13.6
Android Studio 3.2
AVD Pixel 2 Android 9 (API 28)

サンプルプロジェクト

GitHubに公開しています。併せてご覧ください。

Kotlinで書く

プロジェクトを作成するだけですが、スクリーンショットを載せておきます。
Kotlinにチェックします。

とりあえずKitKatにします。

Google Mapを使いたいので「Google Maps Activity」で。

まず現在地を取得する

  1. パーミッションの確認と追加します。

    AndroidManifest.xmlにパーミッションが必要になります。前述の実行時のパーミッション リクエストとは異なりますのですぐに終わります。プロジェクト作成時に「Google Maps Activity」 を選択すると付与されていると思いますが確認して無ければ追加します。

    AndroidManifest.xml
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    
  2. Google Play Servicesを導入します。
    今回はFusedLocationProviderClientLocationRequestなどを利用します。これらGoogle Play Servicesの機能を利用するためにbuild.gradle以下を記述します。

    build.gradle
    implementation 'com.google.android.gms:play-services-location:15.0.1'
    

    build.gradleをご覧頂きましたが、筆者の環境では以下にエラー線がありますが無視して動作させます。なお、この記事の終わりの方で「補足」として解消する方法についても解説します。現在地の取得を優先します

    build.gradle
    implementation 'com.android.support:appcompat-v7:28.0.0'
    
  3. 緯度と経度を取得します。
    公式を参考にしながらプログラムを書きます。「Google Maps Activity」が意味無くなりましたが、シンプルにしたいのでMapActivity.ktを以下のように書き換えました。

    MapActivity.kt
    package com.example.devnokiyo.locationsample
    
    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.os.Looper
    import android.widget.Toast
    import com.google.android.gms.location.*
    
    class MapsActivity : AppCompatActivity() {
        // 位置情報を取得できるクラス
        private lateinit var fusedLocationClient : FusedLocationProviderClient
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            fusedLocationClient = FusedLocationProviderClient(this)
    
            // どのような取得方法を要求
            val locationRequest = LocationRequest().apply {
                // 精度重視(電力大)と省電力重視(精度低)を両立するため2種類の更新間隔を指定
                // 今回は公式のサンプル通りにする。
                interval = 10000                                   // 最遅の更新間隔(但し正確ではない。)
                fastestInterval = 5000                             // 最短の更新間隔
                priority = LocationRequest.PRIORITY_HIGH_ACCURACY  // 精度重視
            }
    
            // コールバック
            val locationCallback = object : LocationCallback() {
                override fun onLocationResult(locationResult: LocationResult?) {
                    // 更新直後の位置が格納されているはず
                    val location = locationResult?.lastLocation ?: return
                    Toast.makeText(this@MapsActivity,
                        "緯度:${location.latitude}, 経度:${location.longitude}", Toast.LENGTH_LONG).show()
                }
            }
    
            // 位置情報を更新
            fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
        }
    }
    

    この状態ではfusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())にエラー線が表示されていますが無視してエミュレーターで動かして見ます。動いているように見えていますが実は動いていません。

    AVDはAndroid 9でありAndroid 6以降の実行時のパーミッション リクエストを実装しておらずパーミッションを許可出来ないからです。実装が完了しない間は手動で許可します。
    Settings > Security and location > location > App-level permissions
     LocationSampleに許可を与えます。

    もう一度、エミュレーターで動かしますと如何でしょうか。Toastで緯度と経度が表示されています。

    ここまでで位置情報を取得することができました
    AVDで動作させているときに手動でパーミッションを許可しているにも関わらず、サンプルコードを緯度と経度が表示されないときはLocationを手動で入力してみてください。

  4. 定期取得をやめてみます
    サンプルコードでは10秒毎に緯度と経度が表示されていると思います。カーナビアプリなどリアルタイムに位置情報を取得したい場合、定期的に位置情報を取得します。しかし、筆者の作成したいアプリは起動時だけ位置情報を取得出来れば良いので、一度位置情報を取得したら定期取得をやめます。

MapsActivity.kt
        // コールバック
        val locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {
                    :
                    :
                // 初回起動でしか位置情報を取得しないサンプルなので位置情報の定期取得を止める。
                fusedLocationClient.removeLocationUpdates(this)
            }
        }

この状態でエミュレーターを動かすと一度だけ緯度と経度が表示されるようになりました。

補足

標準で作成されたbuild.gradleのエラーはバージョンの競合を意味しています。

build.gradle
implementation 'com.android.support:appcompat-v7:28.0.0'

以下を追加してあげれば解消します。Android 9(API 28)未満のAPIを利用すればこの問題は発生しないかもしれません。

build.gradle
    implementation 'com.android.support:support-v4:28.0.0'

終わりに

筆者自身初めて位置情報を取得するアプリを作成することになり調査したので、今後作込むときにどのような挙動を示すか未知数ですが、ひとまずはLocationManagerを利用しない方法で位置情報が取得できました。実機でも動作していました。旧世代向けの環境ではまだテストしていません。筆者環境ではGoogle Maps Activityを利用して地図上に現在地を表示したのですが、記事が長くなりそうでしたので位置情報の取得に留めさせて頂きました。記事を書きながら断念したパターンです