foursquared の GPS 処理を確認してみる

以下から配布されてる Foursquare for Android の GPS 処理を確認。

hg で取得して main/src/com/joelappenna/foursquared/location 配下がターゲットなのかどうなのか。以下の二つのファイルがあります。

  • BestLocationListener.java
  • LocationUtils.java

BestLocationListener を使ってるクラスは若干の例外はあるものの、Foursquared というクラス一発な模様です。とりあえず両方を参照しつつ中身を掘ってみます。

とりあえず

Foursquared.java の上から順に検索しながら確認。requestLocationUpdates というメソドが定義されてます。

    public BestLocationListener requestLocationUpdates(boolean gps) {
        mBestLocationListener.register(
                (LocationManager) getSystemService(Context.LOCATION_SERVICE), gps);
        return mBestLocationListener;
    }

ちなみに mBestLocationListener という属性ですが、定義というか宣言時にオブジェクト生成して参照を代入してます。引用は略します。
で、ここでは BestLocationListener#register というメソドを呼び出してますね。早速中を見てみると

    public void register(LocationManager locationManager, boolean gps) {
        if (DEBUG) Log.d(TAG, "Registering this location listener: " + this.toString());
        long updateMinTime = SLOW_LOCATION_UPDATE_MIN_TIME;
        long updateMinDistance = SLOW_LOCATION_UPDATE_MIN_DISTANCE;
        if (gps) {
            updateMinTime = LOCATION_UPDATE_MIN_TIME;
            updateMinDistance = LOCATION_UPDATE_MIN_DISTANCE;
        }

引数の gps の真偽によって変数の初期設定してますね。で、

        List<String> providers = locationManager.getProviders(true);
        int providersCount = providers.size();
        for (int i = 0; i < providersCount; i++) {
            String providerName = providers.get(i);
            if (locationManager.isProviderEnabled(providerName)) {
                updateLocation(locationManager.getLastKnownLocation(providerName));
            }
            // Only register with GPS if we've explicitly allowed it.
            if (gps || !LocationManager.GPS_PROVIDER.equals(providerName)) {
                locationManager.requestLocationUpdates(providerName, updateMinTime,
                        updateMinDistance, this);
            }
        }

Provider なリストを取得してそれぞれについて以下に処理をしてますね。

  • 取り出した Provider が有効なら updateLocation メソド呼び出し
    • ここは一旦スルーします
  • 引数の gps が真なら全ての Provider に対して requestLocationUpdates 発行
    • ここで最初に設定した変数を使ってます (更新タイミングの調整)
    • gps が偽の場合は GPS_PROVIDER 以外のソレについて requestLocationUpdates 発行になるのか

updateLocation メソド

確認してみます。上に引用した部分では以下な形で呼びだしとなってました。

                updateLocation(locationManager.getLastKnownLocation(providerName));

これ、null が戻る場合もありますのでそのあたりも考慮しているはず。そのあたりが以下なのかどうか。

        // Cases where we only have one or the other.
        if (location != null && mLastLocation == null) {
            if (DEBUG) Log.d(TAG, "updateLocation: Null last location");
            onBestLocationChanged(location);
            return;

        } else if (location == null) {
            if (DEBUG) Log.d(TAG, "updated location is null, doing nothing");
            return;
        }

null ならそのまま return してますね。上側の分岐は location が有効だけど mLastLocation が null の場合で onBestLocationChanged というメソドを呼び出して return しています。先に onBestLocationChanged の中身を見ておきます。

    synchronized public void onBestLocationChanged(Location location) {
        if (DEBUG) Log.d(TAG, "onBestLocationChanged: " + location);
        mLastLocation = location;
        setChanged();
        notifyObservers(location);
    }

mLastLocation に値を代入して Observable な変更通知処理をしています。これ、おそらくこのメソドの他の箇所でも使ってるはずですね。
元に戻ります。ローカル変数の初期設定をしてます。

        long now = new Date().getTime();
        long locationUpdateDelta = now - location.getTime();
        long lastLocationUpdateDelta = now - mLastLocation.getTime();
        boolean locationIsInTimeThreshold = locationUpdateDelta <= LOCATION_UPDATE_MAX_DELTA_THRESHOLD;
        boolean lastLocationIsInTimeThreshold = lastLocationUpdateDelta <= LOCATION_UPDATE_MAX_DELTA_THRESHOLD;
        boolean locationIsMostRecent = locationUpdateDelta <= lastLocationUpdateDelta;

        boolean accuracyComparable = location.hasAccuracy() || mLastLocation.hasAccuracy();
        boolean locationIsMostAccurate = false;
        if (accuracyComparable) {
            // If we have only one side of the accuracy, that one is more
            // accurate.
            if (location.hasAccuracy() && !mLastLocation.hasAccuracy()) {
                locationIsMostAccurate = true;
            } else if (!location.hasAccuracy() && mLastLocation.hasAccuracy()) {
                locationIsMostAccurate = false;
            } else {
                // If we have both accuracies, do a real comparison.
                locationIsMostAccurate = location.getAccuracy() <= mLastLocation.getAccuracy();
            }
        }

これら、何なのかというと onBestLocationChaged メソドを呼び出すかどうかの条件判定のための変数を設定しているようです。呼び出し条件としては

  • accuracyComparable で locationIsMostAccurate で locationIsInTimeThreshold の時
  • あるいは locationIsInTimeThreshold で lastLocationIsInTimeThreshold でない時

という事になっている模様。
ええと順に意味を確認してみます。

  • accuracyComparable
    • location.hasAccuracy() 又は mLastLocation.hasAccurasy() が真なら真
    • 精度 (?) を持ってれば真
    • comparable て何だ
  • locationIsMostAccrate
    • これちょっと設定が複雑ですね
    • 初期値は偽
    • accuracyComparable で
      • 引数な location の hasAccuracy() が真で mLastLocation.hasAccuracy() が偽 なら真
      • 引数な location の hasAccuracy() が偽で mLastLocation.hasAccuracy() が真 なら偽
      • 両方真なら location および mLastLocation の getAccuracy() の値を比較して location の値が mLastLocation の値以下であれば真
  • locationIsInTimeThreshold
    • 現在時刻と location.getTime() の値の差分が LOCATION_UPDATE_MAX_DELTA_THRESHOLD (1000 * 60 * 5) 以下であれば真
    • ここの意味がちょっとよく分からんな

最初の条件としては精度というやつで条件判定してるんですが精度って何なんでしたっけ。
あるいはもう一方の条件としては

  • lastLocationIsInTimeThreshold
    • 現在時刻と mLastLocation.getTime() の値の差分が LOCATION_UPDATE_MAX_DELTA_THRESHOLD (1000 * 60 * 5) 以下であれば真

む、getAccuracy で戻るのは以下なのかどうか

  • android.location.Criteria.ACCURACY_COARSE
    • 値は 2
  • android.location.Criteria.ACCURACY_FINE
    • 値は 1

小さい方が精度的には良い、と判定できる模様。なので精度が落ちていなければ対象と見ているんですね。

ちょっと纏め

ええと、BestLocationListener#register を呼び出すと基本的には

  • BestLocationListener#updateLocation 呼び出し
  • LocationManager#requestLocationUpdates 呼び出し

ということになってますね。おそらくは updateLocation では最初の条件のどちらかが真になって return という形になるのかどうか。

とりあえず

メシの支度ということで中断します。別途追記の方向。あるいは試験用のプロジェクトをでっちあげて云々するかも。

追記

メシ食ったので続きに着手。unregister は以下。

    public void unregister(LocationManager locationManager) {
        if (DEBUG) Log.d(TAG, "Unregistering this location listener: " + this.toString());
        locationManager.removeUpdates(this);
    }

そのまんま、ですね。あるいは getLastKnownLocation が以下。

    synchronized public Location getLastKnownLocation() {
        return mLastLocation;
    }

こちらも mLastLocation を戻すのみ。つうかこの属性ってどうやって変更されるのか、というと updateLocation から呼び出される onBestLocationChanged メソドによって、ということになってます。updateLocation をこのクラスから呼び出しているのは以下。

  • onLocationChanged
  • updateLastKnownLocation

ええと、onLocationChanged は callback メソドですね。updateLastKnownLocation の定義は以下になってます。

    synchronized public void updateLastKnownLocation(LocationManager locationManager) {
        List<String> providers = locationManager.getProviders(true);
        for (int i = 0, providersCount = providers.size(); i < providersCount; i++) {
            String providerName = providers.get(i);
            if (locationManager.isProviderEnabled(providerName)) {
                updateLocation(locationManager.getLastKnownLocation(providerName));
            }
        }
    }

getLastKnownLocation を渡しているのか。

試験してみるか

ちょっと要件というか仕様を列挙してみよう。

  • 試験用の Activity の属性としてオブジェクトの参照を保持
  • onCreate あたりで register メソドを呼び出しておく
  • updateLastKnownLocation メソドを呼び出して getLastKnownLocation を呼び出してみる
  • Google Map 作っておいてボタン押す度に getLastKnownLocation 呼ぶかどうか
  • Observable を上手に使ってみたいんだけどどうすりゃ良いのかな
    • Observer な interface を実装したクラスを作って update メソドを書けば良い模様
    • つうことは Activity 側で Observer な interface を実装して update を書けば良いのか

これでどの程度の精度で位置情報を取得できるかを確認してみれば良いのか。