알림을 종류별로 묶어 사용자가 개별 제어하게 하는 단위. Android 8(O)부터 필수다.
val channel = NotificationChannel(
"chat", "채팅 메시지", NotificationManager.IMPORTANCE_HIGH
).apply {
description = "새 메시지가 도착하면 알립니다"
enableVibration(true)
}
NotificationManagerCompat.from(ctx).createNotificationChannel(channel)
왜 채널이 생겼나
예전엔 알림이 하나의 스위치였다
- 광고 알림이 싫어서 끄면 → 중요한 배송 알림도 함께 꺼진다
- 사용자는 앱 알림을 통째로 끄거나 앱을 지웠다
채널을 나누면
- "마케팅" 만 끄고 "주문 상태" 는 켜 둘 수 있다
- 앱 입장에서도 중요한 알림이 살아남는다
중요도는 만들 때 정해지고 낮출 수만 있다
-
IMPORTANCE_HIGH — 소리 + 헤드업 배너
-
IMPORTANCE_DEFAULT — 소리
-
IMPORTANCE_LOW — 소리 없음
-
IMPORTANCE_MIN — 상태바 아이콘 없음
-
한 번 만든 채널의 중요도는 코드로 올릴 수 없다 (사용자 통제권)
- 바꾸려면 채널 id 를 새로 만들어야 한다
- 채널 설계를 처음에 신중히 한다
Android 13+ 런타임 권한
if (Build.VERSION.SDK_INT >= 33) {
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
권한이 없으면 알림이 조용히 무시된다 — 예외도 안 난다
요청 시점이 중요하다
- 앱 첫 실행에 바로 물으면 대부분 거부한다
- "알림을 받을 이유" 가 생긴 맥락에서 묻는다 (주문 완료 직후 등)
FCM — 서버가 앱을 깨운다
class MyFirebaseService : FirebaseMessagingService() {
override fun onNewToken(token: String) { sendTokenToServer(token) }
override fun onMessageReceived(msg: RemoteMessage) {
// data 메시지는 앱이 직접 알림을 만든다
showNotification(msg.data["title"], msg.data["body"])
}
}
두 종류의 메시지
notification 메시지 앱이 백그라운드면 시스템이 알림을 직접 만든다
→ onMessageReceived 가 안 불린다 (제어권이 없다)
data 메시지 항상 onMessageReceived 가 불린다
→ 알림 모양·중복 제거·로컬 저장을 직접 제어한다
대부분 data 메시지가 낫다
토큰은 언제든 바뀐다 (앱 재설치 · 데이터 삭제 · 복원)
- onNewToken 에서 서버에 갱신한다
- 서버는 만료 토큰(UNREGISTERED) 을 받으면 정리한다
FCM 은 배달을 보장하지 않는다 — 중요한 데이터는 알림이 아니라 서버에서 다시 받는다
면접 함정
- ❌ "채널을 매번 새로 만들면 설정이 초기화된다" → 같은 id면 무시된다. 사용자가 바꾼 설정이 이긴다.
- ❌ "FCM data 메시지는 즉시 도착한다" → Doze 모드에서 지연된다. 즉시성이 필요하면 high priority를 쓰되 남용하면 제한된다.