新卒グループ開発演習で位置情報を用いたAndroidアプリを開発しました。
以下は位置情報の取得に関する備忘録です。
実装要件
- 精度の高いGPSを優先的に利用して位置を取得
- GPSが利用できない場合は、基地局情報を用いて位置を取得
- 基地局情報も利用可能でない場合は位置情報に関する設定画面を開き、ユーザーに位置情報を使うことを促す
実装ポイント
Permissionの設定
- Android6.0からは、位置情報を用いるにはPermissionの設定が必要 ※そもそもPermissionとは
設定する位置情報関連のPermissionは2種類
- Fine : GPSデバイスを使った精度が高い位置情報を取得する
- Corse: ネットワークを使って、GPSが使えない屋内やビルの谷間などで大まかな位置情報を取得する
- AndroidManifestには以下のように書き加える
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
リスナーの登録
onLocationChanged
- 位置情報が変化すると、onLocationChangedがコールバックされる
- getLatitude()とgetLongitude()を使って緯度経度を取得
onProviderEnabled
- ロケーションプロバイダが利用可能になるとコールバックされる
onProviderDisabled
- ロケーションプロバイダが利用不可能になるとコールバックされる
onstatusChanged
- ロケーションステータスが変わるとコールバックされる
LocationManagerのインスタンス生成
isProviderEnabled
- 引数にとったロケーションプロバイダが利用可能かどうか調べる
requestLocationUpdates
- LocationManagerからrequestLocationUpdatesを使って位置情報をアップデートするメソッド
- 位置情報を使わなくなったら、removeUpdates を使って位置情報の更新をやめる必要がある
まとめると、このようになった
public class MainActivity extends AppCompatActivity implements LocationListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Fine か Coarseのいずれかのパーミッションが得られているかチェックする
// 本来なら、Android6.0以上かそうでないかで実装を分ける必要がある
if (ActivityCompat.checkSelfPermission(getApplication(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(getApplication(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
/** fine location のリクエストコード(値は他のパーミッションと被らなければ、なんでも良い)*/
final int requestCode = 1;
// いずれも得られていない場合はパーミッションのリクエストを要求する
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode );
return;
}
// 位置情報を管理している LocationManager のインスタンスを生成する
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
String locationProvider = null;
// GPSが利用可能になっているかどうかをチェック
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
locationProvider = LocationManager.GPS_PROVIDER;
}
// GPSプロバイダーが有効になっていない場合は基地局情報が利用可能になっているかをチェック
else if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
locationProvider = LocationManager.NETWORK_PROVIDER;
}
// いずれも利用可能でない場合は、GPSを設定する画面に遷移する
else {
Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(settingsIntent);
return;
}
/** 位置情報の通知するための最小時間間隔(ミリ秒) */
final long minTime = 500;
/** 位置情報を通知するための最小距離間隔(メートル)*/
final long minDistance = 1;
// 利用可能なロケーションプロバイダによる位置情報の取得の開始
// FIXME 本来であれば、リスナが複数回登録されないようにチェックする必要がある
locationManager.requestLocationUpdates(locationProvider, minTime, minDistance, this);
// 最新の位置情報
Location location = locationManager.getLastKnownLocation(locationProvider);
if (location != null) {
TextView textView = (TextView) findViewById(R.id.location);
textView.setText(String.valueOf( "onCreate() : " + location.getLatitude()) + "," + String.valueOf(location.getLongitude()));
}
}
//位置情報が通知されるたびにコールバックされるメソッド
@Override
public void onLocationChanged(Location location){
TextView textView = (TextView) findViewById(R.id.location);
textView.setText(String.valueOf("onLocationChanged() : " + location.getLatitude()) + ":" + String.valueOf(location.getLongitude()));
}
//ロケーションプロバイダが利用不可能になるとコールバックされるメソッド
@Override
public void onProviderDisabled(String provider) {
//ロケーションプロバイダーが使われなくなったらリムーブする必要がある
}
//ロケーションプロバイダが利用可能になるとコールバックされるメソッド
@Override
public void onProviderEnabled(String provider) {
//プロバイダが利用可能になったら呼ばれる
}
//ロケーションステータスが変わるとコールバックされるメソッド
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// 利用可能なプロバイダの利用状態が変化したときに呼ばれる
}
}
はまったところ
- requestLocationUpdatesの引数である、位置情報を通知するための最小距離間隔(メートル)を0に設定すると、onLocationChangedが呼ばれないことがある
- 1以上にする必要があると思われる
今後の課題
- getLastKnownLocationで最初に位置を取得してから、onLocationChangedで二回目以降位置情報を取得するまで時間がかかってしまう
- そもそもアクティビティに処理を色々書くのではなく機能ごとにクラス分けする必要がある
感想
- 位置情報を取得する実装自体はそんなにむずかしくなかった
- コールバックメソッドが厳密にどのような条件で呼ばれるかが、複数の記事を読んでも不透明のまま
- そのあたりをどう実装したらいいのかベストな方法がまだわからない
福成毅
2年目のiOSエンジニアです!