通知タップでActivityが再描画される原因、PendingIntentのrequestCode被りだった

  • Android
  • Kotlin

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

何が起きたか

サービス経由の通知はタップしてもActivityが再描画されないのに、別のローカル通知(サービスを経由しない方)をタップすると毎回再描画される。両方とも同じMainActivityを開くIntentで、PendingIntentのflagsも揃えてあった。

結論

原因はPendingIntentのrequestCodeが両方とも同じだったこと。

PendingIntentは中身のIntentそのもので区別されるわけじゃない。同一性はIntent.filterEquals()が見る要素(action, data, type, component, categories)とrequestCode、呼び出し元のパッケージ名の組み合わせで決まる。putExtraで入れたExtraは判定に入らない。

なので、起動先のComponent(今回はMainActivity)が同じでrequestCodeも同じだと、Extraの中身が違っていてもAndroidは「同じPendingIntent」とみなす。FLAG_UPDATE_CURRENTを付けていると、後から作られた方でその同一PendingIntentが上書きされる形になる。サービス側とローカル通知側、両方でrequestCode = 0を使っていたので、片方の挙動がもう片方に影響していた。

flowchart TD
    A[PendingIntent.getActivity呼び出し] --> B{component/action/data等が一致?}
    B -- 不一致 --> C[別のPendingIntentとして新規作成]
    B -- 一致 --> D{requestCodeも一致?}
    D -- 不一致 --> C
    D -- 一致 --> E[同一PendingIntentとみなす]
    E --> F[FLAG_UPDATE_CURRENTで中身が上書きされる]

対処

requestCodeを通知の種類ごとに分ける。

サービス側

PendingIntent.getActivity(
context,
1,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

ローカル通知側

PendingIntent.getActivity(
context,
2,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

通知ごとにユニークなIDがあるなら、それをrequestCodeにそのまま使う手もある(requestCode = notificationId)。

再描画自体を抑えたい場合、そこはPendingIntentのflagsじゃなくてIntent側のflagsで制御する話になる。

val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
}

FLAG_ACTIVITY_SINGLE_TOPはActivityがスタックのトップにいる場合にそのまま再利用、FLAG_ACTIVITY_CLEAR_TOPは既存インスタンスを前面に持ってくる、FLAG_ACTIVITY_REORDER_TO_FRONTはタスク内の既存インスタンスを最前面に並び替える。Manifest側のlaunchModeをsingleTaskにしておくとonNewIntent()が呼ばれるようになる。

分かったこと

PendingIntentの同一性はIntentの中身全部じゃなくて、filterEquals()が見る一部の要素とrequestCodeで決まる。Extraが違うだけでは別扱いにならない。同じActivityに向けたPendingIntentを複数種類使うなら、requestCodeかcomponent/action等のfilterEquals対象のどちらかを意図的に変えないと、意図しない上書きが起きる。