🍳

FusedLocationで位置情報を継続的に取得する![ver21.0.1]

2023/08/23に公開

はじめに

Androidで位置情報を取得する時にはFusedLocationを使います。
FusedLocationはAndroid 向けのシンプルでバッテリー効率の高い位置情報 APIと言われています。

そんなFusedLocationですが、
implementation 'com.google.android.gms:play-services-location:20.0.0'から
implementation 'com.google.android.gms:play-services-location:21.0.0'
へのアップデートで位置情報の継続更新リクエストの書き方が変わりました。

本記事ではver21の書き方を記します。

環境

  • Android Studio Flamingo | 2022.2.1 Patch 2
  • compileSdk 33
  • kotlin 1.8.20

完成アプリ

スタートボタンを押すと位置情報を更新し始めて
ストップボタンを押すと止まります。

ソースコード

ver20との違いやプログラムの解説はこの後書きます。

位置情報を取り扱うのでまずManifestに位置情報を使うと書きましょう。

Androidmanifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

FusedLocationを使うために依存関係にplay-services-locationを追加します。

Build.Gradle(app)
dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    implementation 'com.google.android.gms:play-services-location:21.0.1'
}

位置情報を取り扱うクラスをLocationSensorとして、MainActivityはLocationSensorの持つLiveDataを監視する形にします。

LocationSensor.kt
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.pm.PackageManager
import android.location.Location
import android.os.Looper
import androidx.core.app.ActivityCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.android.gms.location.*

class LocationSensor(private val activity: Activity) {

    private val fusedLocationClient: FusedLocationProviderClient =
        LocationServices.getFusedLocationProviderClient(activity)

    private var locationCallback: LocationCallback? = null

    private val _location: MutableLiveData<Location> = MutableLiveData()
    val location: LiveData<Location> = _location

    var run: Boolean = false

    @SuppressLint("MissingPermission")
    fun start() {
        if (checkLocationPermission()) {
            val locationRequest: LocationRequest.Builder =
                LocationRequest.Builder(1000)
                    .setPriority(Priority.PRIORITY_HIGH_ACCURACY)

            locationCallback = object : LocationCallback() {
                override fun onLocationResult(locationResult: LocationResult) {
                    for (location in locationResult.locations) {
                        _location.postValue(location)
                    }
                }
            }

            fusedLocationClient.requestLocationUpdates(
                locationRequest.build(),
                locationCallback as LocationCallback,
                Looper.getMainLooper()
            )

        }
        run = true
    }

    fun requestLocationPermission() {
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            LOCATION_PERMISSION_REQUEST_CODE
        )
    }

    fun stop() {
        locationCallback?.let {
            fusedLocationClient.removeLocationUpdates(it)
            locationCallback = null
        }
        run = false
    }

    private fun checkLocationPermission(): Boolean {
        return ActivityCompat.checkSelfPermission(
            activity,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    }

    companion object {
        private const val LOCATION_PERMISSION_REQUEST_CODE = 1001
    }
}

MainActivityはLocationSensorのインスタンスを持ち、Observerで監視して画面に位置情報を出します。

MainActivity.kt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.Observer

class MainActivity : AppCompatActivity() {

    lateinit var textView: TextView
    lateinit var startButton: Button
    lateinit var stopButton: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textView = findViewById(R.id.textview)
        startButton = findViewById(R.id.button_start)
        stopButton = findViewById(R.id.button_stop)

        val locationSensor = LocationSensor(this)
        locationSensor.requestLocationPermission()

        stopButton.isEnabled = false

        locationSensor.location.observe(this, Observer {
            textView.text = "lat = ${it.latitude}\n long = ${it.longitude}"
        })

        startButton.setOnClickListener {
            if (!locationSensor.run) {
                locationSensor.start()
                startButton.isEnabled = false
                stopButton.isEnabled = true
            }
        }

        stopButton.setOnClickListener {
            if (locationSensor.run) {
                locationSensor.stop()
                startButton.isEnabled = true
                stopButton.isEnabled = false
            }
        }

    }
}

解説

Priority

LocationRequestにはPriorityを設定しましょう。
位置情報を最適化して電池の消耗を抑える

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

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

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

”PRIORITY_NO_POWER”は端末内の位置情報を更新しません。
なのでバックグラウンドで別アプリが位置情報を更新していたり、一回地図アプリを起動して位置情報を更新したりすると取得する位置情報の値も変わります。
逆にそうしないと更新されません(一敗)

20と21の違い

まずlocationRequestの作り方が変わりました。

20ではcreateしていましたが

val locationRequest: LocationRequest.Builder =
                LocationRequest.Builder(1000)
                    .setPriority(Priority.PRIORITY_HIGH_ACCURACY)

このようにメソッドチェーンになりました。

また、アップデートリクエストは

必要な引数の型が変わりました。(locationRequestが変わったため)

なので

fusedLocationClient.requestLocationUpdates(
                locationRequest.build(),
                locationCallback as LocationCallback,
                Looper.getMainLooper()
            )

と書きます。

おわりに

というわけで今回はFusedLocationを使って位置情報を継続的に取る方法を紹介しました。
FusedLocationの説明に書いてあるとおり位置情報はGPSだけでなくWi-Fiの電波強度なども使って推定しています。

なぜGPSが届かないはずの駅構内でも地図アプリは動くのかを見ると少し位置情報に詳しくなるかもしれません。

参考サイト

https://akira-watson.com/android/kotlin/fusedlocationproviderclient.html
https://www.jisei-firm.com/android_develop32/

Discussion