こんにちは、アプリ開発者の@HituziANDOです。
この記事では、AndroidのLocationManagerを使って位置情報をなるべく取得する方法を紹介します。"なるべく"取得するとは、位置情報の精度よりも取得できることそのものに重きを置いています。というのも、先日、LocationManagerを使ったアプリを作っていたのですが、位置情報が全然取得できないという事象に見舞われました。原因はGPS測位のみ有効にしていて、屋内で測位していたため、取得に時間がかかった or 取得できなかったようです。
GPS測位とネットワーク測位
AndroidのLocationManagerにはGPS測位とネットワーク測位の2種類があります。GPS測位とは衛星を使った位置測位であるのに対し、ネットワーク測位とは基地局やWi-Fiアクセスポイントを使った位置測位です。それぞれの特徴を大まかにまとめました。
|
GPS測位 |
NW測位 |
精度 |
高い |
低い |
測定時間 |
遅い |
速い |
バッテリー消費 |
多い |
少ない |
上記より、粗くてもとにかく位置情報を取りたいという場合はNW測位を使い、精度が最重要という場合はGPS測位を使うと良さそうです。ということで、なるべく取得できるようにするためには、NW測位を採用すると良いです。
ところで、この2つの測位方法ですが、同時には片方しか使えないわけではなく、同時に両方使うことができます。以下の実装例は同時に両方を使う例です。
LocationManagerを使った実装
AndroidManifest.xml
AndroidManifest.xmlに以下のパーミッションを追加します。ACCESS_FINE_LOCATIONはGPS測位用、ACCESS_COARSE_LOCATIONはNW測位用です。
<uses-permission androidname="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission androidname="android.permission.ACCESS_COARSE_LOCATION" />
どちらのパーミッションもProtection level: dangerousなため、ユーザ認可が必要です。
MainActivityクラス
ここでは、MainActivityクラスで位置測位するものとします。(この画面が表示されているときだけ位置測位します)
private const val REQUEST_CODE_PERMISSIONS = 1
class MainActivity : AppCompatActivity() {
private val locationManager: LocationManagerUtil by lazy {
val manager = LocationManagerUtil(this)
manager.listener = locationListener
manager
}
private val locationListener: LocationManagerUtil.Listener = object : LocationManagerUtil.Listener {
override fun onLocationUpdated(location: Location) {
Log.d("""
provider: ${location.provider}
lat: ${location.latitude}
lon: ${location.longitude}
acc: ${location.accuracy}
""")
}
}
private var isPermissionDenied = false
set(value) {
field = value
if (!value) {
locationManager.start()
}
else {
locationManager.stop()
}
}
override fun onResume() {
super.onResume()
isPermissionDenied = !checkSelfPermissions()
}
override fun onPause() {
super.onPause()
locationManager.stop()
}
private fun checkSelfPermissions(): Boolean {
val deniedPermissions = arrayListOf<String>()
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
if (deniedPermissions.size > 0) {
ActivityCompat.requestPermissions(this, deniedPermissions.toTypedArray(), REQUEST_CODE_PERMISSIONS)
return false
}
return true
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (grantResults.isNotEmpty()) {
val deniedPermissions = arrayListOf<String>()
grantResults.forEachIndexed { index, result ->
if (result != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[index])
}
}
isPermissionDenied = deniedPermissions.isNotEmpty()
if (isPermissionDenied) {
TODO
}
}
return
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
ポイントは位置情報の権限をチェックして、ユーザに認可されているならば位置測位を開始するようにしています。もし、権限が与えられていない場合は、権限を要求し、その結果でもって位置測位を開始するか判断しています。
LocationManagerUtilクラスは後述するLocationManagerのラッパークラスです。
LocationManagerUtilクラス
private const val MIN_INTERVAL = 10 * 1000L
private const val MIN_DISTANCE = 3.0F
class LocationManagerUtil(context: Context) {
interface Listener {
fun onLocationUpdated(location: Location)
}
var listener: Listener? = null
var currentLocation: Location? = null
private set
private var isStarted = false
private val locationManager: LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val locationListener: LocationListener = object : LocationListener {
override fun onLocationChanged(location: Location?) {
TODO
currentLocation = location
location?.apply { listener?.onLocationUpdated(this) }
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderEnabled(provider: String?) {
}
override fun onProviderDisabled(provider: String?) {
}
}
@SuppressLint("MissingPermission")
fun start() {
if (!isStarted) {
isStarted = true
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_INTERVAL, MIN_DISTANCE, locationListener)
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, MIN_INTERVAL, MIN_DISTANCE, locationListener)
}
}
fun stop() {
if (isStarted) {
isStarted = false
locationManager.removeUpdates(locationListener)
}
}
}
GPS測位とNW測位を開始しています。いずれも位置情報が更新されるとLocationListenerのonLocationChangedメソッドが呼ばれます。MIN_INTERVALとMIN_DISTANCEは位置情報が更新される頻度を調整するパラメータです。
上記の実装により、GPS測位できない状況下でもNW測位で救うことができる可能性があります。ただし、GPS測位もNW測位もできる場合、onLocationChangedメソッドが2重で呼ばれるため、どちらかより良い結果を採用するなど、ひと手間加えた方が良いと思います。
さいごに
AndroidのLocationManagerを使って位置情報を取得する方法を紹介しました。ところで、Androidの公式ドキュメントでは、FusedLocationProviderClientを使う方法が紹介されています。最近はこちらを使った方が良いのかもしれません。