Engineering note · Android

안드로이드 Notification과 PendingIntent의 사용기

2026년 07월 31일이현수
태그androidnotification

Notification 터치 시 PendingIntent를 이용한 액티비티 재사용(launchMode, Intent Flags) 및 Extra 데이터 갱신(FLAG_UPDATE_CURRENT, FLAG_IMMUTABLE) 정리.

앱 서비스에서 중요한 푸시 메시지나 백그라운드 작업을 알리기 위해 Notification 을 활용한다. 알림을 클릭했을 때 앱이 의도대로 포그라운드로 올라오지 않거나, 이전 알림의 딥링크 데이터만 계속 넘어오는 버그가 있었다.

PendingIntent의 역할과 권한 위임

Notification에서 PendingIntent로 기존 Activity를 실행하는 흐름

PendingIntent는 말 그대로 '지연된 인텐트'다. 시스템이나 NotificationManager, AlarmManager 같은 외부 프로세스가 원본 앱을 대신해 지정된 시점에 Intent 를 실행하도록 권한을 위임하는 토큰 객체다.

Android 12부터 보안 강화를 위한 Flag

Android 12(API 31) 이상부터는 PendingIntent 생성 시 FLAG_IMMUTABLEFLAG_MUTABLE을 명시적으로 선언하지 않으면 런타임 크래시가 발생한다.

kotlin
val intent = Intent(this, MainActivity::class.java).apply {
  putExtra("push_target", "notice_123")
}

// Android 12 이상 대응: 변경 불가능한 인텐트 선언
val pendingIntent = PendingIntent.getActivity(
  this,
  0,
  intent,
  PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

외부에서 Extra 를 조작하지 못하도록 하려면 FLAG_IMMUTABLE 을 쓰고, 알림 창에서 입력을 받아 Intent 데이터를 수정해야 하는 경우에만 FLAG_MUTABLE을 사용한다.

Notification 클릭 시 앱 재시작 방지

알림을 클릭했을 때 이미 실행 중이던 앱이 onCreate부터 다시 초기화되는 현상은 인텐트 플래그 설정 미비가 원인이다.

kotlin
val intent = Intent(this, MainActivity::class.java).apply {
  flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
  • FLAG_ACTIVITY_SINGLE_TOP: 이미 액티비티가 떠 있다면 새로 생성하지 않고 기존 Activity의 onNewIntent()를 트리거한다.
  • FLAG_ACTIVITY_CLEAR_TOP: 액티비티 위에 쌓여 있던 태스크들을 정리하고 띄우려는 액티비티를 포그라운드로 끌어올린다.

Manifest의 launchMode="singleTop" 또는 singleTask 설정과 함께 위 플래그를 넘기면 포그라운드로 매끄럽게 복귀시킬 수 있다.

푸시 메시지 Extra 데이터가 안 바뀜

여러 개의 알림을 연달아 띄울 때, 알림 1번의 데이터(noti_id: 1)만 계속 수신되고 알림 2번의 데이터(noti_id: 2)가 덮어씌워지지 않는 버그가 일어날 때가 있다.

원인은 안드로이드 시스템이 동일한 requestCodeIntent.filterEquals 기준의 PendingIntent를 내부에서 캐싱하기 때문이다. 유니크 requestCodeFLAG_UPDATE_CURRENT 조합해서 겹치지 않도록 하자.

kotlin
// 1. notificationId별 유니크한 requestCode 지정
val notificationId = pushPayload.id.hashCode()

val pendingIntent = PendingIntent.getActivity(
  this,
  notificationId, // 고정값 0 대신 유니크 ID 사용
  intent,
  PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

FLAG_UPDATE_CURRENT 플래그로 캐싱된 PendingIntent가 존재해도 새로운 Extra 데이터로 최신화를 강제한다. 유니크 requestCode:고정값 고유 숫자를 넘겨 별개의 인텐트 객체로 시스템이 등록하도록 만든다.

FileUriExposedException 대응 (FileProvider)

알림을 클릭했을 때 로컬 저장소의 다운로드 파일(file://)을 띄우려고 하면 Android 7.0(API 24) 이상에서는 보안 정책상 android.os.FileUriExposedException 오류로 앱이 강제 종료된다..

로컬 절대 경로를 직접 노출하는 대신 FileProvidercontent:// 스킴을 활용한다. 임시 읽기 권한(FLAG_GRANT_READ_URI_PERMISSION)을 부여해 주어야 안전하게 열 수 있음

kotlin
val contentUri = FileProvider.getUriForFile(
  context,
  "${applicationContext.packageName}.fileprovider",
  targetFile
)

val intent = Intent(Intent.ACTION_VIEW).apply {
  setDataAndType(contentUri, "application/pdf")
  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

좋아요와 댓글

댓글 남기기

댓글 0개

댓글을 불러오는 중입니다.