位置情報といえばGPSと思う人が多いとは思いますが、スマホでの位置情報はGPS以外にWiFiや電話網を駆使して短時間で効率の良い情報を取得しています
AndroidではFusedLocationProviderClientを使うと簡単に測位することが可能
2021.2.1
FusedLocationProviderClient
融合された位置予測プロバイダ(FusedLocationProvider)と訳されていますがどういうことでしょうか
GPSは単体での初期起動では位置情報を衛星から取得するのに40分程度かかったりします。また衛星からの電波はほとんどホワイトノイズに近い微弱電波でそれを捕まえるために比較的電力も使います。
それをカバーするためにWifiや電話回線網などによって位置情報を得ることができます。
それぞれGPS、Wifi などの切り分けをプログラマーがいちいちコードで記述するのは大変です。これを、まとめて位置情報を得ることができるのが FusedLocationProvider です。
また、デバイスの電池の消費を最適化も重要なタスクで位置情報はバッテリーコストがかかります
アプリの権限をリクエスト
位置情報は個人情報に類するため、権限リクエストをして、アプリ起動後に許可を得るように設定します
マニュフェストに以下のGPSなどのACESS設定と Google play services の設定が必要です
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
マニフェストにこれらの権限取得を宣言します。
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <application ... |
checkSelfPermission()を使ってpermissionが既に許可されているか確認します
1 2 3 4 5 |
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } |
permissionチェックのリクエスト結果を受け取る
1 2 3 4 5 6 7 8 9 10 |
val requestPermissionLauncher = registerForActivityResult( RequestPermission()) { isGranted: Boolean -> if (isGranted) { fusedLocation() } else { val toast = Toast.makeText( this,"これ以上なにもできません", Toast.LENGTH_SHORT) toast.show() } } |
またbuild.gradleに「play-services-location」を設定しておきます
バージョンは適宜合わせます
1 2 3 4 |
dependencies { implementation 'com.google.android.gms:play-services-location:20.0.0' ... } |
測位の設定
FusedLocationProviderClient:
FusedLocationProviderClient のインスタンスの生成
1 2 |
val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) |
getLastLocation():
直近の(端末として計測した最後の)位置情報の取得。
1 2 3 4 5 6 |
fusedLocationClient.lastLocation .addOnSuccessListener(this) { location -> if (location != null) { // } } |
レアケースとして情報が無くて null が返ってくることもあります。
サンプルコード(1)
getLastLocation() を使った直近の位置情報を取得するケースで、
MainActivityでPermissionの確認をして、許可された場合に位置情報を取得します。
パーミッションは確認だけです。
MainActivity.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
//package your.package.name import android.Manifest import android.content.pm.PackageManager import android.os.Bundle import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts.RequestPermission import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices class MainActivity : AppCompatActivity() { private val requestPermissionLauncher = registerForActivityResult( RequestPermission() ) { isGranted: Boolean -> if (isGranted) { fusedLocation() } else { val toast = Toast.makeText( this, "これ以上なにもできません", Toast.LENGTH_SHORT ) toast.show() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { requestPermissionLauncher.launch( Manifest.permission.ACCESS_FINE_LOCATION ) } else { fusedLocation() } } private fun fusedLocation() { // 冗長ですが if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return } // 最後に確認された位置情報を取得 val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) fusedLocationClient.lastLocation .addOnSuccessListener(this) { location -> if (location != null) { val textView1 = findViewById<TextView>(R.id.text_view1) val textView2 = findViewById<TextView>(R.id.text_view2) // Logic to handle location object // 緯度の表示 val str1 = "Latitude:" + location.latitude textView1.text = str1 // 経度の表示 val str2 = "Longitude:" + location.longitude textView2.text = str2 } } } } |
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="20dp" android:textColor="#44f" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.501" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.2" /> <TextView android:id="@+id/text_view2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:textColor="#f44" android:layout_margin="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.3" /> </androidx.constraintlayout.widget.ConstraintLayout> |
Google play services の play-services-location ライブラリーを build.gradle のdependenciesに設定します。バージョンは適宜変更してください。
build.gradle
1 2 3 4 5 |
... dependencies { implementation 'com.google.android.gms:play-services-location:20.0.0' ... } |
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <application ... </application> </manifest> |
更新リクエスト
移動している場合などでは位置情報の更新が必要になり、継続的な位置情報取得は
バッテリー消費が増加しすぎないように考慮する必要があります。
現在地の更新情報リクエストに使うのが、
1 2 3 4 5 |
public Task<Void> requestLocationUpdates (LocationRequest request, LocationCallback callback, Looper looper) // request: 位置情報リクエスト // callback: 結果を受け取るコールバック // looper: UIスレッドに反映させる |
位置情報リクエストの設定:
LocationRequest を作成して以下のパラメータを設定します。
- 更新間隔:setInterval()
- 更新データを受信する頻度をミリ秒単位で設定
- 最短更新間隔:setFastestInterval()
- 最短の更新間隔を設定
- setInterval()よりも短い間隔で更新するメリットがなければ設定は不要
- 優先度:setPriority()
- PRIORITY_HIGH_ACCURACY
- 高精度の位置情報を取得
主に精度重視のためGPSが優先的に使われる
- 高精度の位置情報を取得
-
- バッテリー消費を抑えたい場合、精度は100m程度と悪くなる
主にwifi,電話網での位置情報が主となるPRIORITY_BALANCED_POWER_ACCURACY
- バッテリー消費を抑えたい場合、精度は100m程度と悪くなる
-
- バッテリー消費を抑えたい場合、精度は10kmと悪くなるPRIORITY_LOW_POWER
- PRIORITY_NO_POWER
- 位置情報取得をアプリが自ら測位しない、
他のアプリで得られた位置情報は入手できる
- 位置情報取得をアプリが自ら測位しない、
- PRIORITY_HIGH_ACCURACY
1 2 3 4 5 |
locationRequest = LocationRequest.create().apply{ interval = 10000 fastestInterval = 5000 priority = Priority.PRIORITY_HIGH_ACCURACY } |
位置情報リクエストを作成:
LocationCallback コールバックを使用して更新を取得
requestLocationUpdates() を呼び出しLocationRequest オブジェクトのインスタンスと LocationCallback を渡す。
コールバック メソッドが受け取る引数には、位置情報の緯度と経度を格納するリスト形式の Location オブジェクトが含まれています。
更新を停止:
位置情報の更新を停止するには、removeLocationUpdates() を呼び出して止めます。
サンプルコード(2)
位置情報の更新ができるようにまとめてみます。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
//package your.package.name import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import android.Manifest import android.content.pm.PackageManager import android.os.Bundle import android.os.Looper import android.widget.TextView import android.widget.Toast import com.google.android.gms.location.* class MainActivity : AppCompatActivity() { private lateinit var locationCallback: LocationCallback private lateinit var locationRequest: LocationRequest private lateinit var fusedLocationClient: FusedLocationProviderClient private var requestingLocationUpdates = false private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> if (isGranted) { requestingLocationUpdates = true } else { val toast = Toast.makeText( this, "これ以上なにもできません", Toast.LENGTH_SHORT ) toast.show() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } else { requestingLocationUpdates = true } locationRequest = LocationRequest.create().apply{ interval = 10000 fastestInterval = 5000 //priority = LocationRequest.PRIORITY_HIGH_ACCURACY priority = Priority.PRIORITY_HIGH_ACCURACY } locationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { for (location in locationResult.locations) { val textView1 = findViewById<TextView>(R.id.text_view1) val textView2 = findViewById<TextView>(R.id.text_view2) // 緯度の表示 val str1 = " Latitude:" + location.latitude textView1.text = str1 // 経度の表示 val str2 = " Longitude:" + location.longitude textView2.text = str2 } } } fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) } private fun startLocationUpdates() { if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return } fusedLocationClient.requestLocationUpdates( locationRequest, locationCallback, Looper.getMainLooper() ) } private fun stopLocationUpdates() { fusedLocationClient.removeLocationUpdates(locationCallback) } override fun onResume() { super.onResume() if (requestingLocationUpdates) { startLocationUpdates() } } override fun onPause() { super.onPause() stopLocationUpdates() } } |
レイアウトその他のコードはサンプルコード(1)と同じです。
関連記事:
- FusedLocationProviderClient による位置情報の取得
- 複数の権限、LOCATIONとCAMERAをリクエスト
- FusedLocationProvider からGoogle Map地図の表示
- Kotlin でGPS位置情報を取得するアプリを作る
- アプリの権限リクエスト
References:
直近の位置情報を取得する – Android Developers
現在地の更新情報をリクエストする – Android デベロッパー
位置情報の設定を変更する – Android デベロッパー
FusedLocationProviderClient
Get the last known location | Android Developers
Runtime Permissions | Android Developers
Making Your App Location-Aware | Android Developers
LocationRequest | Android Developers
googlesamples/android-play-location – GitHub
ActivityCompat | Android Developers