
developer.android.com
뷰의 알림 정보 | Views | Android Developers
뷰 기반 앱의 알림 개요
앱 서비스에서 중요한 푸시 메시지나 백그라운드 작업을 알리기 위해 Notification 을 활용한다. 알림을 클릭했을 때 앱이 의도대로 포그라운드로 올라오지 않거나, 이전 알림의 딥링크 데이터만 계속 넘어오는 버그가 있었다.
PendingIntent의 역할과 권한 위임
developer.android.com
PendingIntent | API reference | Android Developers

PendingIntent는 말 그대로 '지연된 인텐트'다. 시스템이나 NotificationManager, AlarmManager 같은 외부 프로세스가 원본 앱을 대신해 지정된 시점에 Intent 를 실행하도록 권한을 위임하는 토큰 객체다.
Android 12부터 보안 강화를 위한 Flag
Android 12(API 31) 이상부터는 PendingIntent 생성 시 FLAG_IMMUTABLE 나 FLAG_MUTABLE을 명시적으로 선언하지 않으면 런타임 크래시가 발생한다.
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부터 다시 초기화되는 현상은 인텐트 플래그 설정 미비가 원인이다.
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)가 덮어씌워지지 않는 버그가 일어날 때가 있다.
원인은 안드로이드 시스템이 동일한 requestCode와 Intent.filterEquals 기준의 PendingIntent를 내부에서 캐싱하기 때문이다. 유니크 requestCode와 FLAG_UPDATE_CURRENT 조합해서 겹치지 않도록 하자.
// 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 오류로 앱이 강제 종료된다..
로컬 절대 경로를 직접 노출하는 대신 FileProvider 로 content:// 스킴을 활용한다. 임시 읽기 권한(FLAG_GRANT_READ_URI_PERMISSION)을 부여해 주어야 안전하게 열 수 있음
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개