Android x Google Play Services #4 Location API の Geofencing で地図上のある範囲への出入りを通知する

2013.06.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Geofencing (ジオフェンシング) とは

Geofencing は Location API の機能で、地図上のある地点にバーチャルな範囲(フェンス)を作成し、ユーザーがその範囲に出たり入ったりしたことを検知する機能です。詳しくは以下のまとめサイトに載っているので参考にしてください。

さらに Location API の Geofencing には以下のような特徴があります。

シンプルでパワフル

Geofence (範囲) を追加・削除することができます。また複数の Geofence を管理する機能があり、入ったとき・出たときのフィルタリングが可能です。

省電力に最適化

位置情報の更新頻度を Geofence とユーザーの行動 (歩行かドライブ中かなど) に基づいて調整してくれるので、電力消費にやさしいです。

ということで今回は Location API の Geofencing 機能を使って「ある範囲への出入りを通知する」機能を実装してみたいと思います!

Geofencing を使ってみる

ということで早速実装してみたいと思います。今回も Google Play Services APK の有無確認は割愛します。
まずは前回同様 AndroidManifest.xml に権限を追加します。

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

次に LocationClient の実装です。これは前回も使用したクラスで Location API を操作するクラスになります。GooglePlayServicesClient.ConnectionCallbacksGooglePlayServicesClient.OnConnectionFailedListener も必要なので実装します。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // LocationClient の生成
    mLocationClient = new LocationClient(this, mConnectionCallbacks, mOnConnectionFailedListener);
    mLocationClient.connect();
}

/**
 * 接続時・切断時のコールバック.
 */
private GooglePlayServicesClient.ConnectionCallbacks mConnectionCallbacks = new GooglePlayServicesClient.ConnectionCallbacks() {
    @Override
    public void onConnected(Bundle bundle) {
        Toast.makeText(self, "onConnected", Toast.LENGTH_LONG).show();
        // Geofenceを登録
        addGeofence();
    }
    @Override
    public void onDisconnected() {
        Toast.makeText(self, "onDisconnected", Toast.LENGTH_LONG).show();
    }
};

/**
 * 接続失敗時のコールバック.
 */
private GooglePlayServicesClient.OnConnectionFailedListener mOnConnectionFailedListener = new GooglePlayServicesClient.OnConnectionFailedListener() {
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Toast.makeText(self, "onConnectionFailed", Toast.LENGTH_LONG).show();
    }
};

次に17行目に書いた addGeofence() メソッドを作ります。ここで Geofence を作成し LocationClient に登録します。

private void addGeofence() {
    // Geofence の作成
    // 緯度
    double latitude = 35.697239;
    // 経度
    double longitude = 139.774719;
    // 半径(メートル)
    float radius = 100;

    Geofence.Builder builder = new Geofence.Builder();
    builder.setRequestId("classmethod_fence");
    builder.setCircularRegion(latitude, longitude, radius);
    builder.setExpirationDuration(Geofence.NEVER_EXPIRE);
    builder.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT);

    ArrayList<Geofence> geofences = new ArrayList<Geofence>();
    geofences.add(builder.build());

    // PendingIntent の生成
    Intent intent = new Intent(self, GeofencesActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    // Geofences の登録
    mLocationClient.addGeofences(geofences, pendingIntent, mOnAddGeofencesResultListener);
}

Geofence の登録は LocationClient.addGeofences() メソッドを使います。
第一引数には Geofence クラスのリストを渡します。これは Geofence.Builder クラスで緯度・経度・範囲(メートル)を設定し create() メソッドでインスタンス化します。setTransitionTypes() で出るときだけ通知するか、入るときだけ通知するかなど設定することができます。
第二引数は PendingIntent をセットします。Service でも Activity でも何でもいいです。Geofence の範囲に出入りしたときにこの PendingIntent に通知されます。今回は同じ Activity に通知されるようにしました。
第三引数は LocationClient.OnAddGeofencesResultListener をセットします。Geofence の登録が完了したときに onAddGeofencesResult() メソッドが呼び出されます。

/**
 * Geofenceを登録したときのコールバック.
 */
private LocationClient.OnAddGeofencesResultListener mOnAddGeofencesResultListener = new LocationClient.OnAddGeofencesResultListener() {
    @Override
    public void onAddGeofencesResult(int i, String[] strings) {
        Toast.makeText(self, "onAddGeofencesResult", Toast.LENGTH_LONG).show();
    }
};

あとはこの Activity に通知されるので Activity を singleTask にして onNewIntent() で表示を更新するようにします。例として範囲に入ったら背景を緑に、範囲から出たら背景を赤にセットするような実装にしました。

<activity
        android:name=".GeofencesActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    int transitionType = LocationClient.getGeofenceTransition(intent);
    int color = transitionType == Geofence.GEOFENCE_TRANSITION_ENTER ? Color.GREEN : Color.RED;
    findViewById(android.R.id.content).setBackgroundColor(color);
}

以上で実装完了です。ソース全体は以下のようになります。

GeofenceActivity.java

package jp.classmethod.android.sample.locationapi;

import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.LocationClient;

import java.util.ArrayList;

public class GeofencesActivity extends FragmentActivity {

    private final GeofencesActivity self = this;
    private LocationClient mLocationClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // LocationClient の生成
        mLocationClient = new LocationClient(this, mConnectionCallbacks, mOnConnectionFailedListener);
        mLocationClient.connect();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        int transitionType = LocationClient.getGeofenceTransition(intent);
        int color = transitionType == Geofence.GEOFENCE_TRANSITION_ENTER ? Color.GREEN : Color.RED;
        findViewById(android.R.id.content).setBackgroundColor(color);
    }

    private void addGeofence() {
        // Geofence の作成
        // 緯度
        double latitude = 35.697239;
        // 経度
        double longitude = 139.774719;
        // 半径(メートル)
        float radius = 100;

        Geofence.Builder builder = new Geofence.Builder();
        builder.setRequestId("classmethod_fence");
        builder.setCircularRegion(latitude, longitude, radius);
        builder.setExpirationDuration(Geofence.NEVER_EXPIRE);
        builder.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT);

        ArrayList<Geofence> geofences = new ArrayList<Geofence>();
        geofences.add(builder.build());

        // PendingIntent の生成
        Intent intent = new Intent(self, GeofencesActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        // Geofences の登録
        mLocationClient.addGeofences(geofences, pendingIntent, mOnAddGeofencesResultListener);
    }

    /**
     * 接続時・切断時のコールバック.
     */
    private GooglePlayServicesClient.ConnectionCallbacks mConnectionCallbacks = new GooglePlayServicesClient.ConnectionCallbacks() {
        @Override
        public void onConnected(Bundle bundle) {
            Toast.makeText(self, "onConnected", Toast.LENGTH_LONG).show();
            // Geofenceを登録
            addGeofence();
        }
        @Override
        public void onDisconnected() {
            Toast.makeText(self, "onDisconnected", Toast.LENGTH_LONG).show();
        }
    };

    /**
     * 接続失敗時のコールバック.
     */
    private GooglePlayServicesClient.OnConnectionFailedListener mOnConnectionFailedListener = new GooglePlayServicesClient.OnConnectionFailedListener() {
        @Override
        public void onConnectionFailed(ConnectionResult connectionResult) {
            Toast.makeText(self, "onConnectionFailed", Toast.LENGTH_LONG).show();
        }
    };

    /**
     * Geofenceを登録したときのコールバック.
     */
    private LocationClient.OnAddGeofencesResultListener mOnAddGeofencesResultListener = new LocationClient.OnAddGeofencesResultListener() {
        @Override
        public void onAddGeofencesResult(int i, String[] strings) {
            Toast.makeText(self, "onAddGeofencesResult", Toast.LENGTH_LONG).show();
        }
    };
}

Geofences の設定は弊社の住所の半径100メートル以内としました。実行するともちろんここに居るので緑になります!

geofencing

まとめ

以上、Geofences を使って「ある範囲への出入りを検知する」機能の実装方法でした。ツール系・ゲーム系・ソーシャル系などどんなアプリにも使える便利な機能ですので、ぜひとも覚えておきましょう。次回は Activity Recognition を使ってみたいと思います。

参考