FCM notification 에서 삽집을 한 이후 PendingIntent 에 대한 이해가 부족하다는 것을 깨달았고 더 자세히 공부해보고자 한다.
나는 notification builder 에 PendingIntent 를 첨부하여 notification 클릭시 원하는 activity 로 랜딩되도록 만들었다. 이때 PendingIntent 에 첨부되는 intent에 extra 데이터를 넣고, 이 데이터에 따라 activity 에서 하는 행위가 정해진다. 그런데 이 intent extra 데이터를 변경해도 activity에 도달해서는 매번 같은 동작만 반복하는 상황이 발생했다.
// FirebaseMessagingService
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val notificationBuilder: NotificationCompat.Builder =
getNotificationBuilder(...)
// sdkVersion 에 따라서 다른 방식으로
// notificationBuilder를 얻어오기 위한 개인 정의함수
addPendingIntent(notificationBuilder, remoteMessage.data)
notificationBuilder.setAutoCancel(true)
notificationManger.notify(pushIdx, notificationBuilder.build())
}
private fun addPendingIntent(
builder: NotificationCompat.Builder,
data: Map<String, String>) {
val loginIntent = Intent(this, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
// putIntentExtras
}
val pendingIntent = PendingIntent.getActivity(
applicationContext,
data["idx"]!!,
loginIntent,
PendingIntent.FLAG_IMMUTABLE
)
builder.setContentIntent(pendingIntent)
}
이 코드에서 log를 찍어서 푸시 종류에 따라 매번 다른 extra 를 넣는다는걸 파악했다. 하지만 activity 에서 log를 찍으면 결국에는 매번 동일한 extra 를 전달 받는 문제가 있었는데, 처음에는 그 원인이 onNewIntent 콜백을 재정의하지 않았기 때문이라고 생각하였다.
하지만 여러 레퍼런스를 참고하여 문제를 해결하고 log 를 찍어보니 onNewIntent 콜백은 전혀 호출되지 않는다는 것을 알아서 이게 문제의 원이이 아님을 알았다.
문제의 원인을 파악하기 위해 개념을 공부했고 pendingIntent 가 갖는 고유의 특성 때문에 발생하는 문제라는 것을 알 수 있었다.
Intent 는 안드로이드 어플리케이션 컴포넌트 (AAC)를 실행하기 위해 안드로이드 시스템에게 알려주는 역할을 하는 객체이다. 이러한 Intent 에 대해 추가적인 설명을 덧 붙이고, Intent 가 정의된 어플리케이션이 아닌, 다른 어플리케이션에서 Intent 를 실행하도록 하는 것이 PendingIntent 이다.
이때 덧붙여진 설명에는 언제 포함된 Intent 를 실행할 것인지 등의 설명이 포함된다.
이정도가 내가 원래 알던 개념이었다. 이정도만 알아도 PendingIntent 를 이용하여 서비스를 만드는데 무리가 없다고 생각했지만... 그렇지 않았다.
PendingIntent 자체는 시스템에서 관리되는 토큰에 대한 레퍼런스 값을 가지고 있는 객체이다. 이 토큰은 PendinigIntent 에 포함된 Intent의 행위를 실행시키기 위한 데이터를 가지고 있다.
PendingIntent 객체를 만드는 순간 시스템에 등록이 되기 때문에, 이를 등록한 프로세스가 종료된 이후에도 다른 프로세스에서 PendingIntent 를 실행할 수 있는 것이다.
그리고 나중에 PendingIntent 를 만들었던 프로세스가 재 실행되어 동일한 PendingIntent 를 만들면 동일한 토큰값을 갖게 된다. PendingIntent가 동일하다록 판단하는 조건은 same operation, same Intent action, data, categories, and components, and same flags 이다.
이러한 특징 때문에 인텐트의 extra 데이터만 변경하여 다른 PendingIntent 를 만드는 것은, PendingIntent 자체에 대한 변화가 없기 때문에 결국 제일 처음 시스템에 등록한 토큰값을 재호출 하는 것이 되고, 제일 처음 시스템에 등록한 인텐트의 extra를 가진채 Activity 를 실행하게 된다.
위의 문제 코드는 PendingIntent 가 어떻게 동작하는지도 모르고 쓰고 있었던 것이다. 당연히 매번 같은 intentExtra 를 갖는 액티비티를 실행하게 된 것이다.
이를 해결하는 방법으로는 보통 두가지 방법이 있다.
다수의 pendingIntent 가 필요한 경우, notification 을 예로 들면 여러 notification 이 동시에 떠 있을 필요가 있는 경우에
public boolean filterEquals (Intent other)
함수에서 다르게 두 인텐트를 다르게 인식하도록 특정 요소를 변경할 것.
내가 만드는 서비스의 경우 단 두개의 activity 만 가지고있으며 나머지는 모두 fragment 를 통해 화면을 관리하기 때문에 filterEquals() 을 통해 서로 다른 인텐트라고 인식하도록 하기는 어려울 것이고,
public static PendingIntent getActivity (Context context,
int requestCode,
Intent intent,
int flags)
함수에서 requestCode 를 매번 다르게 줄것.
매번 다른 requestCode 를 주기 위해 server 에서 내려오는 pushIdx 를 줘왔었지만, 그럼에도 불구하고 매번 같은 intentExtra 를 가진 액티비티가 실행되었으므로 이 부분에 대해서는 더 공부가 필요해 보인다.
PendingIntent flag 에 FLAG_CANCEL_CURRENT 혹은FLAG_UPDATE_CURRENT 를 설정하여 기존 intent 를 변경하는 방법이 있다.
나는 이 방법을 사용했고, 문제가 해결되었다.
private fun addPendingIntent(
builder: NotificationCompat.Builder,
data: Map<String, String>) {
val loginIntent = Intent(this, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
// putIntentExtras
}
val pendingIntent = PendingIntent.getActivity(
applicationContext,
data["idx"]!!,
loginIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
builder.setContentIntent(pendingIntent)
}