AndroidではGPSとWifiや基地局GPSを組み合わせた、FusedLocationProvider が推奨されていますが、GPSが一番精度の高い位置情報を取得できます。KotlinでのGPSの設定方法を調べてみました。
2024.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 109 110 111 112 113 114 115 116 |
package com.example.kotlingps import android.os.Bundle import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat 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.provider.Settings import android.util.Log import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts.RequestPermission 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) enableEdgeToEdge() setContentView(R.layout.activity_main) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } 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 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?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:id="@+id/main" 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.1" /> <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.501" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.2" /> </androidx.constraintlayout.widget.ConstraintLayout> |
emulatorで実行します。
最初はpermissionの確認ダイアログが出ます
- アプリの使用時のみ
- 今回のみ
- 許可しない
emulatorの右上にあるアイコン「…」をクリックしてExtended controlsを開きます。
「Location」のタグから地図上の位置、例えば皇居をセットしピンを立てると
住所と緯度経度が下に表示されます
「SAVE POINT」をクリックすると、Save Pointsにリストアップされ
その下にある
「Set Location」をクリックすると、
アプリに緯度経度が表示されます。
これで実機でテストしてみましょう。今回はGPSのみの動作ですので、屋外でテストするためにログをTextViewで出せるようにしました。屋外でも、ビルの谷間はほぼあきらめた方がいいです。衛星が見えても1個2個ですから最低3個、通常は4個補足する必要があります。
GPSの位置を取得するまで、数分かかる場合があります。GPS衛星のカレンダーを持っていれば、最初の補足時間が短くなりますが、無い場合は、衛星からその情報を取得するのです。
実機のテストなどで緯度経度がわかると、Google Mapで簡単に調べられます
google.com/maps?q=[緯度],[軽度]
でChromeで調べられます
google.com/maps?q=35.6827,139.7512
35.6827 -> 35°40’57,7″ と変換されているので注意ですが
関連記事:
- FusedLocationProviderClient による位置情報の取得
- 複数の権限、LOCATIONとCAMERAをリクエスト
- FusedLocationProvider からGoogle Map地図の表示
- Kotlin でGPS位置情報を取得するアプリを作る
- アプリの権限リクエスト
References: