Android 13 이상 푸시 알림 권한(POST_NOTIFICATIONS) 대응 및 타이밍 설계 정리
앱 타겟 버전을 Android 13(API 33) 이상으로 올리면서 알림 기능 운영에 큰 변화가 생겼습니다. 예전에는 알림 채널만 만들어서 푸시를 보내면 사용자가 알아서 설정에서 끄는 방식이었는데, 이제는 POST_NOTIFICATIONS라는 런타임 권한을 직접 받아야 합니다.
새로 설치한 앱은 알림이 기본적으로 꺼져 있기 때문에, 예전 생각만 하고 개발하면 푸시가 아예 안 가는 상황이 발생합니다.
운영을 하다 보니 "코드를 어디에 넣을까"보다 "어느 타이밍에 권한을 요청할까"가 훨씬 중요하더군요. 앱 시작하자마자 아무 설명 없이 팝업부터 띄우면 사용자는 대부분 거부합니다. 게다가 한 번 거부당하면 다시 권한을 유도하기가 꽤 까다롭습니다. 나중에 제가 다시 보려고 실무 기준으로 핵심만 짧게 정리해 둡니다.
1. 핵심 개념: 헷갈리기 쉬운 3가지 요소 분리하기
개발하다 보면 FCM 토큰 발급, 알림 채널 생성, 런타임 권한 요청을 다 같은 하나로 묶어서 생각하기 쉬운데, 이거 확실히 분리해서 처리해야 운영 데이터가 안 꼬입니다.
| 항목 | 필요한 버전 | 역할 및 특징 |
| POST_NOTIFICATIONS | Android 13 이상 | 앱이 기기에 일반 알림을 표시할 수 있는 최종 허가권입니다. |
| NotificationChannel | Android 8.0 이상 | 알림의 종류(배송, 채팅, 공지 등)와 중요도를 나누는 기준입니다. |
| FCM Token | 모든 버전 | Firebase 사용 시 기기를 식별하기 위한 고유 키값입니다. |
주의: FCM 토큰이 정상적으로 발급되었고 서버로 전송 성공했어도, 기기에서
POST_NOTIFICATIONS권한이 거부되어 있으면 사용자는 푸시 알림을 구경도 못 합니다. 두 상태를 매칭해서 서버에서 관리해야 비용과 리소스를 아낍니다.
2. Manifest 설정 및 권한 요청 예시 코드
먼저 AndroidManifest.xml에 권한을 선언해 줍니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
</application>
</manifest>
저의 경우 권한 요청 로직이 꼬이지 않도록 아래처럼 별도 헬퍼 클래스를 만들어서 관리합니다. targetSDK 33 이상에서 shouldShowRequestPermissionRationale를 활용하는 구조입니다.
class NotificationPermissionRequester(
private val activity: ComponentActivity,
private val onGranted: () -> Unit,
private val onDenied: () -> Unit,
) {
private val launcher = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission(),
) { granted ->
if (granted) onGranted() else onDenied()
}
fun requestIfNeeded() {
// Android 13 미만은 런타임 권한이 없으므로 바로 통과
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
onGranted()
return
}
val permission = Manifest.permission.POST_NOTIFICATIONS
when {
// 이미 권한이 있는 경우
ContextCompat.checkSelfPermission(activity, permission) ==
PackageManager.PERMISSION_GRANTED -> {
onGranted()
}
// 사용자가 이전에 거부한 적이 있는 경우 (설명 UI 필요)
activity.shouldShowRequestPermissionRationale(permission) -> {
showRationaleDialog(permission)
}
// 처음 요청하는 경우
else -> {
launcher.launch(permission)
}
}
}
private fun showRationaleDialog(permission: String) {
AlertDialog.Builder(activity)
.setTitle("알림 권한 안내")
.setMessage("주문 배송 상태 및 중요 공지 알림을 받으시려면 알림 권한 허용이 필요합니다.")
.setPositiveButton("계속") { _, _ -> launcher.launch(permission) }
.setNegativeButton("나중에") { _, _ -> onDenied() }
.show()
}
}
3. 권한 요청 타이밍과 거부 시 Fallback 설계
가장 중요한 것은 사용자가 "이 알림이 나한테 필요하겠구나"라고 느끼는 맥락에서 팝업을 띄우는 것입니다.
추천하는 권한 요청 타이밍
주문/예약 앱: 사용자가 결제를 끝내거나 예약을 완료한 직후 ("배송/예약 상태 알림을 보내드릴까요?")
채팅/댓글 앱: 사용자가 특정 글에 댓글을 달거나 채팅방에 참여할 때
설정 화면: 사용자가 직접 '마케팅 정보 알림 수신' 토글을 켤 때
사용자가 거부했을 때 (Fallback) 대책
권한을 거부당했다고 해서 서비스 이용을 막으면 이탈률만 높아집니다. 알림 외에 앱 안에서 확인할 수 있는 대체 UX를 반드시 열어줘야 합니다.
| 알림 종류 | 권한 거부 시 대체 방안 (Fallback) |
| 주문/예약 상태 | 앱 메인 화면이나 마이페이지에 '진행 현황' 배너/인앱 인디케이터 노출 |
| 채팅 및 댓글 | 앱 실행 시 하단 탭에 빨간 배지(badge)를 띄우거나 목록 새로고침 유도 |
| 마케팅/이벤트 | 콘텐츠 이용은 그대로 유지하되, 나중에 설정 메뉴에서 켤 수 있도록 유도 |
4. 실무 테스트 시 체크리스트
실제 기기나 에뮬레이터에서 검증할 때 아래 시나리오별로 동작을 다 확인하셔야 운영 환경에서 빵꾸가 안 납니다.
Android 13 이상 신규 설치: 앱을 처음 켰을 때 권한 허용 전까지는 푸시가 실제로 안 오는지 확인
사용자가 스와이프(뒤로가기)로 팝업 닫음: 권한 상태가 거부로 변하지 않고, 다음 적절한 시점에 다시 팝업을 띄울 수 있는지 확인
기존 앱 업그레이드: Android 12 이하에서 이미 알림 채널을 쓰던 사용자가 13으로 업데이트했을 때 pre-grant(자동 허용) 조건이 맞는지 확인
서버 연동 안전성: 테스트 중 로그캣(Logcat)이나 응답 값에 실제 FCM 토큰이나 개인 식별 데이터가 통째로 찍히지 않는지 확인 (보안 주의)
5. 초보자가 자주 하는 실수 요약
앱 시작하자마자 요청하기: 사용자는 어떤 알림이 오는지도 모른 채 90% 확률로 거절합니다.
FCM 토큰과 권한 동일시하기: 서버에 토큰 발급 완료되었다고 무조건 알림 성공 처리하면 안 됩니다. 권한 상태값도 서버에 같이 인덱싱해 두는 편이 좋습니다.
거부 후 무한 팝업 강제하기: 계속 권한을 달라고 떼쓰는 앱은 사용자가 지워버립니다. 거부 후에는 깔끔하게 포기하고 앱 내 알림 배지나 설정 이동 링크로 유도하는 것이 자연스럽습니다.
앱을 오래 운영하다 보면 결국 기술적인 구현 자체보다 사용자 경험(UX)을 해치지 않는 흐름을 잡는 게 더 까다롭습니다. 런타임 권한 처리가 필요하신 분들은 위 흐름 참고하셔서 적용해 보시기 바랍니다.
댓글
댓글 쓰기