🌏

[Android]ジオフェンスを複数登録する際にはPendingIntentの扱いに気をつける

2024/07/24に公開

要約

ジオフェンスを複数個登録する際、PendingIntentを別々にすると、GEOFENCE_TOO_MANY_PENDING_INTENTSというエラーで5つを超えるジオフェンスの登録に失敗する。

独立したタイミングで複数個のジオフェンスを登録する要件がある場合、PendingIntentは共通の1つのものを使うようにすると良い。

動作環境

com.google.android.gms.play-services-location:21.2.0

Androidのジオフェンスの制限と起きた事象

ジオフェンスの作成と監視

アクティブなジオフェンスは複数作成できます(ただし、1 つのアプリで作成できるジオフェンスはデバイス ユーザーあたり 100 個までです)。

Androidではジオフェンスの作成個数に100個までという制限があります。しかし、登録数が100個に満たない状態でもPendingIntentの作成方法によって5つまでしかジオフェンスを登録できない、という事象に遭遇しました。

どのようなエラーか

処理するPendingIntentを別々にし、複数回ジオフェンスの登録を行うと、5つまでは登録に成功しますが、6つめで登録に失敗します

エラーの内容は、GEOFENCE_TOO_MANY_PENDING_INTENTSというものです。

https://developers.google.com/android/reference/com/google/android/gms/location/LocationStatusCodes#public-static-final-int-geofence_too_many_pending_intents

You have provided more than 5 different PendingIntents to the GeofencingApi.addGeofences(com.google.android.gms.common.api.GoogleApiClient, GeofencingRequest, PendingIntent) call

5つを超える、異なるPendingIntentを、GeofencenfApi.addGeofencesに登録している、という内容です。

問題が起きた実装

1つ1つのジオフェンス登録時にPendingIntentを作成していました。

その際requestCodeを別にし、PendingIntentにはputExtraでデータ取得に使うkeyを入れることで、ジオフェンス通知を受け取った際、どのデータに対する通知を受信したかの判別をすることができていました

PendingIntentの作成
val receiverIntent = Intent(context, CustomReceiver::class.java)
                .setAction(ACTION_CUSTOM_RECEIVER)
                .putExtra(EXTRA_KEY, key)

val geofencePendingIntent = PendingIntent.getBroadcast(
    context,
    // requestCodeはGeofence毎に別のもの
    requestCode,
    receiverIntent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
ジオフェンス設定部分の実装
val geofence =
    Geofence.Builder()
        .setRequestId(data.requestId)
        .setCircularRegion(lat, lon, 500)
        .setExpirationDuration(Geofence.NEVER_EXPIRE)
        .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
        .build()
val geofencingRequest = GeofencingRequest.Builder()
    .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
    .addGeofences(listOf(geofence))
    .build()

val geofencingClient = LocationServices.getGeofencingClient(context)
geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)
    .addOnSuccessListener {
        Log.d("TAG", "Success")
    }
    .addOnFailureListener {
        Log.d("TAG", "Failure")
    }

この方法だと、ジオフェンス毎にPendingIntentが作成されるので、5つを超えた場合にGEOFENCE_TOO_MANY_PENDING_INTENTSとなり登録に失敗してしまいます。

解決方法

Geofence毎にPendingIntentを作成せず、共通のPendingIntentを使うようにします。これによって、GEOFENCE_TOO_MANY_PENDING_INTENTSエラーを回避することができます。

PendingIntentの作成
val receiverIntent = Intent(context, CustomReceiver::class.java)
                .setAction(ACTION_CUSTOM_RECEIVER)

val geofencePendingIntent = PendingIntent.getBroadcast(
    context,
    // COMMON_REQUESTCODEは共通のもの
    COMMON_REQUESTCODE,
    receiverIntent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)

しかし、リクエストコードを共通にするだけで、ジオフェンスの登録を個別にしてしまうと、同じPendingIntentが使われてしまうため、別々のジオフェンスの受信ができなくなってしまいます。
(上書きされるか、以前のものが使われるかはPendingIntent作成時のフラグによって変わります)

そこで、ジオフェンスの登録を個別にすることをやめて、PendingIntentを1つだけ使い、ジオフェンスの登録はまとめて行うようにします。

独立したタイミングでジオフェンス登録が行われる場合でも、まだ発火していないジオフェンスとこれから登録しようとするジオフェンスを1つのPendingIntentを使ってまとめて登録し、上書きされるようにします。

ジオフェンス設定部分の実装
val geofenceList = geofenceDataList.map { data ->
    Geofence.Builder()
        .setRequestId(data.requestId)
        .setCircularRegion(data.lat, data.lon, 500)
        .setExpirationDuration(Geofence.NEVER_EXPIRE)
        .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
        .build()
}
val geofencingRequest = GeofencingRequest.Builder()
    .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
    .addGeofences(geofenceList)
    .build()

val geofencingClient = LocationServices.getGeofencingClient(context)
geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)
    .addOnSuccessListener {
        Log.d("TAG", "Success")
    }
    .addOnFailureListener {
        Log.d("TAG", "Failure")
    }

BroadcastReceiverで受信したジオフェンスの内容を、GeofenceGeofencingEvent.triggeringGeofencesで取得できます

Geofence.requestIdで登録した際のrequestIdが取得できるので、それを使い登録したジオフェンスの識別ができます

ジオフェンスイベント受信時の実装
override fun onReceive(context: Context, intent: Intent) {
    val geofencingEvent = GeofencingEvent.fromIntent(intent) ?: return
    geofencingEvent.triggeringGeofences.forEach { geofence ->
        val requestId = geofence.requestId
        // requestIdを使って処理
    }
}

まとめ

  • Androidのジオフェンスは100個まで登録できる、と思い実装していたら異なるPendingIntent5個までの制限にかかる

  • この制限があるのは、ジオフェンスとNearbyMessagesに関してだけのよう

    • AlarmManagerを使ったアラーム登録などはrequestCodeを別にして別のPendingIntentを複数作る実装で問題ないかと思います
  • 独立したタイミングで複数のジオフェンス登録を行う要件がある場合、PendingIntentは共通の1つのものを使うようにし、まとめて登録を行うと問題が起きない

間違いや、より良い実装方法がありましたらご指摘お願いします 🙇‍♂️

Discussion