AndroidではGPSとWifiや基地局GPSを組み合わせた、FusedLocationProvider が推奨されていますが、GPSが一番精度の高い位置情報を取得できます。KotlinでのGPSの設定方法を調べてみました。
2021.1.1
LocationManager
GPSの実装はここのところ色々と制約がついてきてしまっているので、ここでの内容は一番簡単な以下の設定ですが、実用ではバックグラウンドで処理、Permissionの扱いなどが必要となります。
- GPSを起動するためのもっとも簡単な実装
- 1つのRuntime Permissionのみを想定
- フォアグランドでの実行
GPS簡単な実装
GPSに必要なpermissionは、Android 6.0 Runtime Permission の dangerous permissionに該当するため、ユーザーが許可しないと使えません。これは複雑になるので、GPSとしての機能を確認する実装について考えてみます。
registerForActivityResult:
とりあえずpermissionが許可されない例外は考えないで実装します。
onRequestPermissionsResultは非推奨になり
registerForActivityResult()を使います。
アクティビティの結果を取得する | Android デベロッパー
registerForActivityResult() は、
- ActivityResultContract
- ActivityResultCallback
を受け取り、他のアクティビティを開始するために使用する
- ActivityResultLauncher
を返します。
permissionが既に許可されているかの確認
1 2 3 4 5 6 7 8 |
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // permissionが許可されていない requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } else { // permissionが許可されている } |
permissionチェックのリクエスト結果を受け取る
1 2 3 4 5 6 7 8 |
val requestPermissionLauncher = registerForActivityResult(RequestPermission()){ isGranted: Boolean -> if (isGranted) { // 使用が許可された } else { // 拒否された } } |
Permissionの設定:
AndroidManifestに以下の設定をします。FineとCoarseの2種類あります。
Fineを設定する場合は両方必要
1 2 |
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> |
- FineはGPSデバイスを使った精度が高い位置情報を取得します。
- CoarseはGPSが使えない屋内やビルの谷間などでもネットワークを利用して大まかな情報を得ます。
LocationManager
LocationManagerのインスタンス生成:
GPSだけでなく位置情報を管理しているのがLocationMnagerです。インスタンスは Context.getSystemService(Class)を使って取得します。
1 |
var locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager |
リスナーの登録:
Activityに LocationListener を登録します。
1 2 3 |
class MainActivity : AppCompatActivity(), LocationListener { ... } |
requestLocationUpdates:
LocationManagerからrequestLocationUpdatesを使って位置情報をアップデートするメソッドを呼び出します。
1 2 3 4 5 6 |
locationManager.requestLocationUpdates( provider minTime, minDistance, listener ) |
- provider:
- GPSを使う場合は LocationManager.GPS_PROVIDER 設定します。
ネットワークの場合は、LocationManager.NETWORK_PROVIDE となります。
ただこの設定の場合はGPS_PROVIDERのみの選択で、ユーザーの位置情報設定が「バッテーリー節約」つまりNETWORK_PROVIDEになっていると設定を促すページに飛ぶようにしてあります。
「高精度」あるいは「端末のみ」の選択です。
また、Criteria を使って、低消費電力モードにすることもできます。Criteria
- GPSを使う場合は LocationManager.GPS_PROVIDER 設定します。
- minTime:最小時間間隔[msec]、これは消費電力に大きく関係します
- minDistance:最小距離間隔[m]、アップデートでの最小距離です
- listener:登録したリスナー
onLocationChanged:
位置情報が変化したケースでonLocationChangedにて latitude と longitude を使って緯度経度を取得します。
1 2 3 4 5 6 7 |
override fun onLocationChanged(location: Location) { // Latitude Log.d("debug","Latitude:"+location.latitude) // Longitude Log.d("debug","Longitude:"+location.longitude) } |
サンプルコード
まとめると
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 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 |
//package com.example.testgps import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.os.Bundle import android.provider.Settings import android.util.Log 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 androidx.core.content.ContextCompat class MainActivity : AppCompatActivity(), LocationListener { // lateinit: late initialize to avoid checking null private lateinit var locationManager: LocationManager private val requestPermissionLauncher = registerForActivityResult( RequestPermission() ) { isGranted: Boolean -> if (isGranted) { // 使用が許可された locationStart() } 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 (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } else { locationStart() } } private fun locationStart() { Log.d("debug", "locationStart()") // Instances of LocationManager class must be obtained using Context.getSystemService(Class) locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { Log.d("debug", "location manager Enabled") } else { // to prompt setting up GPS val settingsIntent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) startActivity(settingsIntent) Log.d("debug", "not gpsEnable, startActivity") } if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1000) Log.d("debug", "checkSelfPermission false") return } locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 1000, 50f, this) } override fun onLocationChanged(location: Location) { // Latitude val textView1 = findViewById<TextView>(R.id.text_view1) val str1 = "Latitude:" + location.latitude textView1.text = str1 // Longitude val textView2 = findViewById<TextView>(R.id.text_view2) val str2 = "Longitude:" + location.longitude textView2.text = str2 } override fun onProviderEnabled(provider: String) { } override fun onProviderDisabled(provider: String) { } } |
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" package="com.example.testgps"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <application ... </application> </manifest> |
レイアウトですが、緯度・経度を表示するだけです。
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 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="20dp" tools:context=".MainActivity"> <TextView android:id="@+id/text_view1" android:textSize="20sp" android:layout_margin="20dp" android:textColor="#44f" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/text_view2" android:textSize="20sp" android:textColor="#f44" android:layout_margin="20dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> |
emulatorで実行します。
最初はpermissionの確認ダイアログが出ます
- アプリの使用時のみ
- 今回のみ
- 許可しない
emulatorの右下にあるMore「…」をクリックしてExtended controlsを開きます。
「Location」のタグから適当なマップにマーカーをセットし「Set Location」をクリックすると、アプリに緯度経度が表示されます。
これで実機でテストしてみましょう。今回はGPSのみの動作ですので、屋外でテストするためにログをTextViewで出せるようにしました。屋外でも、ビルの谷間はほぼあきらめた方がいいです。衛星が見えても1個2個ですから最低3個、通常は4個補足する必要があります。
GPSの位置を取得するまで、数分かかる場合があります。GPS衛星のカレンダーを持っていれば、最初の補足時間が短くなりますが、無い場合は、衛星からその情報を取得するのです。
関連記事:
- FusedLocationProviderClient による位置情報の取得
- 複数の権限、LOCATIONとCAMERAをリクエスト
- FusedLocationProvider からGoogle Map地図の表示
- Kotlin でGPS位置情報を取得するアプリを作る
- アプリの権限リクエスト
References: