Geofenceを使ってみる


はじめに

先日ジオフェンスを使う機会がありました。
イメージだけでとても難しいものだろうと思っていたのですが、
使ってみると意外にも(あくまで意外に)さっくりできたので記事にしてみます。

ジオフェンスとは

ある位置を中心に半径xメートルの円(≒フェンス)を作成、その円に対する出入りや滞在をイベントとして取得します。

公式ドキュメント

準備

必要なライブラリを入れておく必要があります。
最新バージョンは2021/09/19現在18.0.0。 参考

build.gradle
dependencies {
    implementation 'com.google.android.gms:play-services-location:18.0.0'
}

また、GPSで位置情報を取得するためパーミッション設定が必要です。 参考

AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 10(API レベル 29)以上をターゲットとする場合以下も -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

ジオフェンスからのイベントを受け取るクラス

ジオフェンスを作成する際にこのクラスに通知するよーという情報が必要なので、先に通知を受け取る側を作っておきます。

GeofenceBroadcastReceiver.kt

class GeofenceBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (context !is Context || intent !is Intent) return

        val geofencingEvent = GeofencingEvent.fromIntent(intent)

        if (geofencingEvent.hasError()) {
            // error時処理
            return
        }

        // イベントの種類によって処理を分ける
        when (geofencingEvent.geofenceTransition) {
            Geofence.GEOFENCE_TRANSITION_ENTER -> Log.d("geofence event", "enter")
            Geofence.GEOFENCE_TRANSITION_EXIT -> Log.d("geofence event", "exit")
            Geofence.GEOFENCE_TRANSITION_DWELL -> Log.d("geofence event", "dwell")
            else -> Log.d("geofence event", "xxx")
        }
    }
}

ジオフェンスから通知されるイベントの種類はいくつかあります。
・Geofence.GEOFENCE_TRANSITION_ENTER
 ⇨ジオフェンスに入ったイベント
・Geofence.GEOFENCE_TRANSITION_EXIT
 ⇨ジオフェンスから出たイベント
・Geofence.GEOFENCE_TRANSITION_DWELL
 ⇨ジオフェンスに指定時間滞在した場合のイベント。ENTERの後に呼ばれる。これを使用する場合は滞在とみなすまでの時間(LoiteringDelay)をジオフェンス生成時に設定してあげる必要があります。 参考

また、BroadcastReceiverを継承しているので、AndroidManifest.xmlに登録しておきましょう。 参考

AndroidManifest.xml
<application
    <receiver android:name=".geofences.GeofenceBroadcastReceiver"/>
</application>

ジオフェンスを生成する

メインどころですね。

さっそく以下にコードを。
(位置情報を得るためにパーミッションが許可されているかチェックが必要ですが、省略します。)

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 諸々省略

        createGeofence()
    }

    private fun createGeofence() {
        // 位置情報許可されているかどうかなどチェックしておく(省略)

        // ジオフェンスの設定
        val geofenceBuilder = Geofence.Builder().apply {
            /**
             * idの設定
             * このidを使用してジオフェンスの削除(remove)などをするので一意に
             */
            setRequestId("geofence")

            /**
             * ジオフェンスの中心地、半径の設定
             * 前2つが緯度経度。最後のが半径(単位はメートル)
             */
            setCircularRegion(35.681283, 139.766935, 100f)

            /**
             * ジオフェンスの有効期限(単位はミリ秒)
             * 設定しておくと、期限を過ぎた場合自動的に削除される
             */
            setExpirationDuration(Geofence.NEVER_EXPIRE)

            /**
             * どのイベントを捕捉するか
             */
            setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)

            /**
             * DWELLを捕捉する場合、以下で滞在とみなすまでの時間を設定する必要がある。(単位はミリ秒)
             * ジオフェンスに入った後、ここで指定した時間が経過した後にDWELLイベントを通知する。
             * TransitionTypesにGeofence.GEOFENCE_TRANSITION_DWELLが含まれていない場合は無視される。
             */
            //setLoiteringDelay(5 * 60 * 1000) // 5分 * 60秒 * 1000m

            /**
             * 応答性を指定。主に電池消費などを考慮する場合に設定。(単位はミリ秒)
             * デフォルトは0、内部的に調整される場合があるとのこと。
             */
            //setNotificationResponsiveness(5 * 60 * 1000)
        }

        val request = GeofencingRequest.Builder().apply {
            // ジオフェンスが作られたタイミングで端末がジオフェンス内にいる場合捕捉するかどうか
            setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            addGeofence(geofenceBuilder.build())
        }

        // ジオフェンスからのイベントを受け取る前述のクラスを設定
        val pendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            Intent(this, GeofenceBroadcastReceiver::class.java),
            PendingIntent.FLAG_UPDATE_CURRENT
        )

        // 実際にジオフェンスを作る
        LocationServices.getGeofencingClient(this).addGeofences(request.build(), pendingIntent)
            .run {
                addOnSuccessListener { Log.d("geofence event", "create success") }
                addOnFailureListener { 
                    Log.d("geofence event", "create failure") 
                    it.printStackTrace()
                }
            }
    }
}

コード内にインラインでコメントをつけてしまっていますが、ジオフェンスには色々設定できるものがあります。 参考

まとめ

ということで意外と簡単にジオフェンスを作成することができました。
まだまだ勉強不足感がありますね。知見を深めるためにも継続してみていきたい分野です。