アンドロイドでの位置情報は端末のGPSとWifiや基地局GPSを組み合わせた、FusedLocationProviderClient が推奨されています。ただ精度が高い位置情報のみを得たい場合は明示的にGPSを使うことになります。
API 29
AndroidX
GPS
簡単な実装を GPSで位置情報を取得するアプリを作る で試しましたがその続きです。
繰り返しになりますが、GPSに必要なpermissionは、Android 6.0 Runtime Permission の dangerous permission に該当するため、ユーザーの許可を得るための実装が必要になります。
Runtime Permission
Android 6.0 Runtime Permission に該当するPermissionため、アプリ起動中に許可を得るように設定します。許可しない場合でも例外で落ちないように実装しないといけません。下のようなフロー処理となります。
- APIが23以上か否か
- False: そのまま処理実行
- 許可しているか否か、checkSelfPermission(…)
- 許可していない場合や、初回起動のケースは次に行く
- 初めは許可していたが、途中で拒否をユーザーが選択するケースもあり得ることに注意
- 許可しているケースは処理に移る
- 許可していない場合や、初回起動のケースは次に行く
- Permissionが何に使われるか説明して許可してもらうように依頼する
- 拒否された場合はこの機能については動作しないが他の処理をする
- 例えばゲームアプリで得点をネットにアップするとき、ユーザーの位置情報もアップしたいという要請に対して拒否しても、ゲームは継続されるということです。
- 許可されて処理の実行
- 拒否された場合はこの機能については動作しないが他の処理をする
GPSの基本的な設定は こちら を参照してください。
サンプルコード: Runtime Permission
以下はMainActivityでPermissionの確認をして、許可されていたら別Activity(LocationActivity.java)に遷移してGPS測位を開始するようにしました。
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 |
package your.package.name; //AndroidX import androidx.appcompat.app.AppCompatActivity; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; //import android.support.v7.app.AppCompatActivity; //import android.support.annotation.NonNull; //import android.support.v4.app.ActivityCompat; //import android.support.v4.content.ContextCompat; import android.os.Build; import android.os.Bundle; import android.Manifest; import android.content.pm.PackageManager; import android.content.Intent; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private final int REQUEST_PERMISSION = 1000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Android 6, API 23以上でパーミッシンの確認 if(Build.VERSION.SDK_INT >= 23){ checkPermission(); } else{ startLocationActivity(); } } // 位置情報許可の確認 public void checkPermission() { // 既に許可している if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){ startLocationActivity(); } // 拒否していた場合 else{ requestLocationPermission(); } } // 許可を求める private void requestLocationPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION); } else { toastMake("許可されないとアプリが実行できません", 10, -100); } } // 結果の受け取り @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_PERMISSION) { // 使用が許可された if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { startLocationActivity(); } else { // それでも拒否された時の対応 toastMake("これ以上なにもできません", 0, -200); } } } // Intent でLocation private void startLocationActivity() { Intent intent = new Intent(getApplication(), LocationActivity.class); startActivity(intent); } private void toastMake(String message, int x, int y){ Toast toast = Toast.makeText(this, message, Toast.LENGTH_LONG); // 位置調整 toast.setGravity(Gravity.CENTER, x, y); toast.show(); } } |
サンプルコード: GPS測位
ここからGPSの測位
LocationActivity.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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
package your.package.name; //AndroidX import androidx.core.app.ActivityCompat; //import android.support.v4.app.ActivityCompat; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Bundle; import android.provider.Settings; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import java.text.SimpleDateFormat; import java.util.Locale; public class LocationActivity extends Activity implements LocationListener { private LocationManager locationManager; private TextView textView; private StringBuilder strBuf = new StringBuilder(); private static final int MinTime = 1000; private static final float MinDistance = 50; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // LocationManager インスタンス生成 locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); textView = findViewById(R.id.text_view); // GPS測位開始 Button buttonStart = findViewById(R.id.button_start); buttonStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startGPS(); } }); // GPS測位終了 Button buttonStop = findViewById(R.id.button_stop); buttonStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { stopGPS(); } }); } protected void startGPS() { strBuf.append("startGPS\n"); textView.setText(strBuf); Log.d("LocationActivity", "gpsEnabled"); final boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); if (!gpsEnabled) { // GPSを設定するように促す enableLocationSettings(); } if (locationManager != null) { Log.d("LocationActivity", "locationManager.requestLocationUpdates"); try { // minTime = 1000msec, minDistance = 50m if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){ // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MinTime, MinDistance, this); } catch (Exception e) { e.printStackTrace(); Toast toast = Toast.makeText(this, "例外が発生、位置情報のPermissionを許可していますか?", Toast.LENGTH_SHORT); toast.show(); //MainActivityに戻す finish(); } } super.onResume(); } @Override protected void onPause() { if (locationManager != null) { Log.d("LocationActivity", "locationManager.removeUpdates"); // update を止める if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){ // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } locationManager.removeUpdates(this); } super.onPause(); } @Override public void onLocationChanged(Location location) { strBuf.append("----------\n"); String str = "Latitude = " +String.valueOf(location.getLatitude()) + "\n"; strBuf.append(str); str = "Longitude = " + String.valueOf(location.getLongitude()) + "\n"; strBuf.append(str); str = "Accuracy = " + String.valueOf(location.getAccuracy()) + "\n"; strBuf.append(str); str = "Altitude = " + String.valueOf(location.getAltitude()) + "\n"; strBuf.append(str); SimpleDateFormat sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.JAPAN); String currentTime = sdf.format(location.getTime()); str = "Time = " + currentTime + "\n"; strBuf.append(str); str = "Speed = " + String.valueOf(location.getSpeed()) + "\n"; strBuf.append(str); str = "Bearing = " + String.valueOf(location.getBearing()) + "\n"; strBuf.append(str); strBuf.append("----------\n"); textView.setText(strBuf); } @Override public void onProviderDisabled(String provider) { } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { switch (status) { case LocationProvider.AVAILABLE: strBuf.append("LocationProvider.AVAILABLE\n"); textView.setText(strBuf); break; case LocationProvider.OUT_OF_SERVICE: strBuf.append("LocationProvider.OUT_OF_SERVICE\n"); textView.setText(strBuf); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: strBuf.append("LocationProvider.TEMPORARILY_UNAVAILABLE\n"); textView.setText(strBuf); break; } } private void enableLocationSettings() { Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivity(settingsIntent); } private void stopGPS(){ if (locationManager != null) { Log.d("LocationActivity", "onStop()"); strBuf.append("stopGPS\n"); textView.setText(strBuf); // update を止める if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } locationManager.removeUpdates(this); } } @Override public void onStop() { super.onStop(); stopGPS(); } } |
一応レイアウト
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 40 41 42 43 44 45 46 47 48 49 50 |
<?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:background="#cfd" tools:context=".MainActivity"> <LinearLayout android:gravity="center" android:background="#4f8" android:orientation="horizontal" android:layout_margin="20dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/button_start" android:text="@string/start" android:textSize="18sp" android:layout_margin="5dp" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" /> <Button android:id="@+id/button_stop" android:text="@string/stop" android:textSize="18sp" android:layout_margin="5dp" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" /> </LinearLayout> <ScrollView android:layout_margin="20dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/text_view" android:textColor="#000" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </ScrollView> </LinearLayout> |
strings.xml
1 2 3 4 5 |
<resources> <string name="app_name">YourAppName</string> <string name="start">Start</string> <string name="stop">Stop</string> </resources> |
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <application ... </activity> <activity android:name=".LocationActivity" /> </application> </manifest> |
これで実機でテストしてみましょう。屋外でテストするためにログをTextViewで出せるようにしました。屋外でも、ビルの谷間はほぼあきらめた方がいいです。衛星が見えても1個2個ですから最低3個、通常は4個捕捉する必要があります。
GPSの位置を取得するまで、数分かかる場合があります。GPS衛星のカレンダーを持っていれば、最初の補足時間が短くなりますが、無い場合は、衛星からその情報を取得するのです。
また一度測位した後のアップデートはこの設定の場合、1秒後で距離50mの変化があるケースです。
測位によって得られる主な情報は:
- getLatitude():緯度[°]
- 南緯がマイナス、北緯がプラスで、WGS84座標系
- getLongitude():経度[°]
- 西経がマイナス、東経がプラスで、WGS84座標系
- getAccuracy():精度[m]
- この緯度経度を中心としてこの半径の円内にある確率が68%ということです。正規分布の標準偏差+/-2σということですね。
- getAltitude():高度
- 単位は[m]でWGS84座標系、高度はGPSではあまり精度がよくありません
- getTime():測位時刻[msec]
- 1970/1/1 0:00からのUTC時間。これを使って時間差を計算してはいけないようで時間が前後することもあり得ます。時間のデルタを計測したい場合は
getElapsedRealtimeNanos を使います。
- 1970/1/1 0:00からのUTC時間。これを使って時間差を計算してはいけないようで時間が前後することもあり得ます。時間のデルタを計測したい場合は
- getSpeed():速度[m/sec]
- 止まっていれば0となります。
- getBearing():方位(水平)[°]
- 北から東へ90°となり0°は北向きを示しています。
などです。これ以外にも色々あります。尚、これはフォアグランドでのGPSを走らせています。バックグランドでの測位はこちらを参考にしてください。
関連ページ:
- FusedLocationProviderClient による位置情報取得
- GPSでの位置情報
- GPS パーミッションを考慮して実装する
- GPSログをテキストで保存、複数の Runtime Permissionの設定
- バックグラウンドでGPSログを取り続けるには
- Runtime Permission, Android 6.0 からの変更
References:
LocationManager | Android Developers
Criteria | Android Developers
Runtime Permissions | Android Developers
Location Strategies | Android Developers
Location | Android Developers