読者です 読者をやめる 読者になる 読者になる

リア充爆発日記

You don't even know what ria-ju really is.

Androidで位置情報を扱うときに使うLocationManagerの使い方の要点まとめ

Android

http://developer.android.com/training/basics/location/locationmanager.html
ここを読んでのまとめメモ。基本的に要約。※は僕の付け足し

作ればわかる! Androidプログラミング 第2版 -SDK4対応- (Smart Mobile Developer)

作ればわかる! Androidプログラミング 第2版 -SDK4対応- (Smart Mobile Developer)

Manifest

  • 用途に応じてACCESS_COARSE_LOCATIONかACCESS_FINE_LOCATIONを追加する
    • ACCESS_COARSE_LOCATIONはネットワークベース(GPS使わない)
    • ACCESS_FINE_LOCATIONはGPSベースで、これを指定するとACCESS_COARSE_LOCATIONも自動的に許可される
まとめ

まあ、だいたいACCESS_FINE_LOCATIONとINTERNETパーミッションが必要になるってことね。

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />

※書く場所はapplicationタグと同列。中じゃないよ。
※あと、開発時にエミュレータの擬似情報を使うときはACCESS_MOCK_LOCATIONも必要。


LocationManagerの参照を取得

  • LocationManagerが位置情報をごにょるためのメインクラス
  • 取得は主にonCreate()でやる。※つーことはFragmentだったらonActivityCreated()内かな・・・。
LocationManager locationManager =
        (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

Location Providerを選択する

  • 位置情報の取得方法によって、取得時間・精度・通信費・電池消費などが変わってくるので、それぞれの特性を考慮して使い分けること※今んとこGPSとネットワークベース(主にWi-fi)しかないはず
    • 例えば取得時間でいうと、通常は精度がより高い方法(GPS)であるほど、精度が低い方法(ネットワークベース)より取得時間がかかる。
  • GPSを使った取得方法を選ぶ場合は以下のとおり
LocationProvider provider =
        locationManager.getProvider(LocationManager.GPS_PROVIDER);

※通信費(原文:monetary cost)ってなんだよ!って思って調べてみたけど、たぶん、将来的に活用するIFっぽくて、現状では考慮されない(全部costはかからない判定)になってるらしい。っても確定した情報ソースは見つけられなくて、StackOverflowに投稿された”推測”からの推測だからね!

  • 一方で条件に応じて、Androidに適切なProviderを選択させることもできる
  • 以下は、精度が高く、通信費がかからない条件設定
    • 常に条件にあったProviderが返されるとは限らないことに注意。
// Retrieve a list of location providers that have fine accuracy, no monetary cost, etc
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setCostAllowed(false);
...
String providerName = locManager.getBestProvider(criteria, true);

// If no suitable provider is found, null is returned.
if (providerName != null) {
   ...
}

Location Providerが利用できるか確認する

  • GPSとか設定で切られてることがあるよ
  • その場合はユーザーに通知して、そのままGPSの設定画面に連れて行ってあげるといいよね。
@Override
protected void onStart() {
    super.onStart();

    // This verification should be done during onStart() because the system calls
    // this method when the user returns to the activity, which ensures the desired
    // location provider is enabled each time the activity resumes from the stopped state.
    LocationManager locationManager =
            (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    final boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

    if (!gpsEnabled) {
        // Build an alert dialog here that requests that the user enable
        // the location services, then when the user clicks the "OK" button,
        // call enableLocationSettings()
    }
}

private void enableLocationSettings() {
    Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    startActivity(settingsIntent);
}

Location Listenerのセットアップ

  • LocationManagerにはいくつか位置情報の更新を受け取る方法がある
  • 一番シンプルなのはLocationListenerのonLocationChanged() コールバックメソッドで拾う方法
  • 更新頻度は時間と移動距離の複合指定
  • 以下のコードは最低10秒間ごとか、10メートル以上動いた時に発火する設定 ※ちなみにこれ、10秒たったらかならず発火、とか10メートル動いたらかならず発火、とかいうわけじゃなくて、更新タイミングを決めるヒントにされるだけだから注意だよ!電池消費を抑えるためにもアプリが1分に1度更新できれば充分、という場合に1分に表示しておけば、それより細かいタイミングで更新はされないよ、という意味らしいですよ。詳細はAPIドキュメントを。
private final LocationListener listener = new LocationListener() {

    @Override
    public void onLocationChanged(Location location) {
        // A new location update is received.  Do something useful with it.  In this case,
        // we're sending the update to a handler which then updates the UI with the new
        // location.
        Message.obtain(mHandler,
                UPDATE_LATLNG,
                location.getLatitude() + ", " +
                location.getLongitude()).sendToTarget();

            ...
        }
    ...
};

mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
        10000,          // 10-second interval.
        10,             // 10 meters.
        listener);

複数の位置情報元からの更新情報をハンドリングする

  • 普通は精度が高いほうが位置情報取得に時間がかかるので、なるべく早く位置を表示させたいときなどは、両方のProvider(GPSとネットワーク)を登録しておく。
  • onLocationChanged() には、両方の更新情報が、更新時間・精度とともに送られてくるので、この2つの情報を判定して更新時間が古かったり、精度が低かったりする情報をうまくフィルタリングするように実装すべし
  • で、そのように実装したサンプルが以下
private static final int TWO_MINUTES = 1000 * 60 * 2;

/** Determines whether one Location reading is better than the current Location fix
  * @param location  The new Location that you want to evaluate
  * @param currentBestLocation  The current Location fix, to which you want to compare the new one
  */
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
    if (currentBestLocation == null) {
        // A new location is always better than no location
        return true;
    }

    // Check whether the new location fix is newer or older
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;

    // If it's been more than two minutes since the current location, use the new location
    // because the user has likely moved
    if (isSignificantlyNewer) {
        return true;
    // If the new location is more than two minutes older, it must be worse
    } else if (isSignificantlyOlder) {
        return false;
    }

    // Check whether the new location fix is more or less accurate
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;

    // Check if the old and new location are from the same provider
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());

    // Determine location quality using a combination of timeliness and accuracy
    if (isMoreAccurate) {
        return true;
    } else if (isNewer && !isLessAccurate) {
        return true;
    } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
        return true;
    }
    return false;
}

/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) {
      return provider2 == null;
    }
    return provider1.equals(provider2);
}

※この辺はテスト書きながら写経するのがいいと思うぞ!あとでチューニングとかするかもしれないからな!



getLastKnownLocation()をうまく使う

  • 位置情報の取得がちょっとも待てない状況もあるかもしれない。そんなときはgetLastKnownLocation()を使おう。
  • でも、この情報は往々にして古いからね。使えるかどうかは自分で判断しないといけないよ。
  • もし使えないと判断して、新しい情報を待つのだとすれば、適切なメッセージをユーザーに表示してあげよう

位置情報の更新を止める

  • 不要な位置情報の取得はしないこと。電池とネットワーク帯域使っちゃうからね。
  • たとえばユーザーが画面を変えたときなんかは以下のようにonStop()でremoveUpdate()をコールして、更新を止めよう
protected void onStop() {
    super.onStop();
    mLocationManager.removeUpdates(listener);
}

※画面変えても取得しづけたほうがいいアプリだったら(Runkeeperとかみたいに)バックグラウンド処理にしたりして対処しようね。



逆ジオコーディングで住所の表示

  • これまで説明した内容で位置情報はとれるけど、数値での情報はユーザーには伝わらないよね。だから住所を表示しよう。
  • GeocoderAPIが使えます。が、これはWebサービスに依存しているので状況によっては使えない場合があるので注意しよう。
    • Android 2.3 (API level 9) からisPresent()で、サービスが生きてるかどうかをチェックできる
  • 以下サンプルコードだけど、AsyncTaskが使われていることに注目しよう。getFromLocation()はブロックするHTTP通信を行うからUIスレッドから直接呼んじゃダメだぞ!※通信が終了するまで、すべてがフリーズするぞ!
private final LocationListener listener = new LocationListener() {

    public void onLocationChanged(Location location) {
        // Bypass reverse-geocoding if the Geocoder service is not available on the
        // device. The isPresent() convenient method is only available on Gingerbread or above.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent()) {
            // Since the geocoding API is synchronous and may take a while.  You don't want to lock
            // up the UI thread.  Invoking reverse geocoding in an AsyncTask.
            (new ReverseGeocodingTask(this)).execute(new Location[] {location});
        }
    }
    ...
};

// AsyncTask encapsulating the reverse-geocoding API.  Since the geocoder API is blocked,
// we do not want to invoke it from the UI thread.
private class ReverseGeocodingTask extends AsyncTask<Location, Void, Void> {
    Context mContext;

    public ReverseGeocodingTask(Context context) {
        super();
        mContext = context;
    }

    @Override
    protected Void doInBackground(Location... params) {
        Geocoder geocoder = new Geocoder(mContext, Locale.getDefault());

        Location loc = params[0];
        List<Address> addresses = null;
        try {
            // Call the synchronous getFromLocation() method by passing in the lat/long values.
            addresses = geocoder.getFromLocation(loc.getLatitude(), loc.getLongitude(), 1);
        } catch (IOException e) {
            e.printStackTrace();
            // Update UI field with the exception.
            Message.obtain(mHandler, UPDATE_ADDRESS, e.toString()).sendToTarget();
        }
        if (addresses != null &s;&s; addresses.size() > 0) {
            Address address = addresses.get(0);
            // Format the first line of address (if available), city, and country name.
            String addressText = String.format("%s, %s, %s",
                    address.getMaxAddressLineIndex() > 0 ? address.getAddressLine(0) : "",
                    address.getLocality(),
                    address.getCountryName());
            // Update the UI via a message handler.
            Message.obtain(mHandler, UPDATE_ADDRESS, addressText).sendToTarget();
        }
        return null;
    }
}


まとめっても、ほぼ意訳になった希ガス・・・。

スマートにプログラミング Android入門編 第3版 SDK4.x対応

スマートにプログラミング Android入門編 第3版 SDK4.x対応