位置情報といえば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が既に許可されているか確認します
アクティビティの結果を取得する | Android デベロッパー
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 11 12 13 |
private final ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { locationStart(); } else { Toast 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 |
FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); |
getLastLocation():
直近の(端末として計測した最後の)位置情報の取得。
1 2 3 4 5 6 7 8 9 |
fusedLocationClient.getLastLocation() .addOnSuccessListener(this, location -> { if (location != null) { // Last Location data } else{ // No Last Location data } }); |
レアケースとして情報が無くて null が返ってくることもあります。
サンプルコード(1)
getLastLocation() を使った直近の位置情報を取得するケースで、
MainActivityでPermissionの確認をして、許可された場合に位置情報を取得します。
パーミッションは確認だけです。
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 |
//package your.package.name; import androidx.activity.result.ActivityResultLauncher; 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.widget.TextView; import android.widget.Toast; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.LocationServices; public class MainActivity extends AppCompatActivity { private TextView textView1, textView2; private final ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { fusedLocation(); } else { Toast toast = Toast.makeText(this, "これ以上なにもできません", Toast.LENGTH_SHORT); toast.show(); } }); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView1 = findViewById(R.id.text_view1); textView2 = findViewById(R.id.text_view2); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissionLauncher.launch( Manifest.permission.ACCESS_FINE_LOCATION); } else { fusedLocation(); } } private void fusedLocation() { // 冗長ですが if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } // 最後に確認された位置情報を取得 FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); fusedLocationClient.getLastLocation() .addOnSuccessListener(this, location -> { if (location != null) { // Logic to handle location object // 緯度の表示 String str1 = "Latitude:" + location.getLatitude(); textView1.setText(str1); // 経度の表示 String str2 = "Longitude:" + location.getLongitude(); textView2.setText(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
- バッテリー消費を抑えたい場合、精度は10kmと悪くなるPRIORITY_LOW_POWER
- 高精度の位置情報を取得
- PRIORITY_NO_POWER
- 位置情報取得をアプリが自ら測位しない、
他のアプリで得られた位置情報は入手できる
- 位置情報取得をアプリが自ら測位しない、
- PRIORITY_HIGH_ACCURACY
-
1 2 3 4 |
locationRequest = LocationRequest.create(); locationRequest.setPriority(Priority.PRIORITY_HIGH_ACCURACY) .setFastestInterval(5000) .setInterval(10000); |
位置情報リクエストを作成:
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 111 112 113 114 115 116 117 118 119 |
//package your.package.name; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import android.Manifest; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; import android.os.Looper; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.LocationCallback; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; public class MainActivity extends AppCompatActivity { private LocationCallback locationCallback; private LocationRequest locationRequest; private FusedLocationProviderClient fusedLocationClient; private boolean requestingLocationUpdates = false; private TextView textView1, textView2; private final ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { requestingLocationUpdates = true; } else { Toast toast = Toast.makeText(this, "これ以上なにもできません", Toast.LENGTH_SHORT); toast.show(); } }); @Override protected void onCreate(Bundle savedInstanceState) { 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; } textView1 = findViewById(R.id.text_view1); textView2 = findViewById(R.id.text_view2); locationRequest = LocationRequest.create(); locationRequest.setPriority( Priority.PRIORITY_HIGH_ACCURACY) .setFastestInterval(5000) .setInterval(10000); locationCallback = new LocationCallback() { @Override public void onLocationResult(@NonNull LocationResult locationResult) { for (Location location : locationResult.getLocations()) { // 緯度の表示 String str1 = " Latitude:" + location.getLatitude(); textView1.setText(str1); // 経度の表示 String str2 = " Longitude:" + location.getLongitude(); textView2.setText(str2); } } }; fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); } private void startLocationUpdates() { if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); } private void stopLocationUpdates() { fusedLocationClient.removeLocationUpdates(locationCallback); } @Override protected void onResume() { super.onResume(); if (requestingLocationUpdates) { startLocationUpdates(); } } @Override protected void onPause() { super.onPause(); stopLocationUpdates(); } } |
レイアウトその他のコードはサンプルコード(1)と同じです。
関連記事:
- FusedLocationProviderClient による位置情報取得
- 複数の権限をリクエストする
- FusedLocationProvider とGoogle Mapで地図を表示
- GPSでの位置情報
- アプリの権限、位置情報をリクエストする実装
References:
直近の位置情報を取得する – Android Developers
現在地の更新情報をリクエストする – Android デベロッパー
位置情報の設定を変更する – Android デベロッパー
FusedLocationProviderClient
アプリの権限をリクエストする