알림·푸시 — Notification·채널·FCM
알림은 "사용자의 주의"를 빌리는 일
앱이 백그라운드에 있어도 사용자에게 닿는 유일한 통로가 **알림(notification)**이고, 서버가 잠든 앱을 깨워 알림을 보내는 것이 **푸시(FCM)**다. 그런데 알림은 사용자의 주의를 빌리는 일이라 — 남용하면 곧바로 차단·삭제로 이어진다. 그래서 현대 안드로이드는 통제권을 사용자에게 넘긴다 — 알림 채널로 종류별로 끄게 하고, Android 13+부터는 알림 자체에 런타임 권한을 건다. 핵심은 *"기술적으로 어떻게 보내나"보다 "맥락 있고 절제된 알림만 보내나"*다. (출처: Android — Notifications / Firebase Cloud Messaging.)
알림 채널 — 통제권을 사용자에게
Android 8+부터 모든 알림은 채널(channel)에 속해야 한다. 왜 채널을 강제할까? — 옛날엔 앱이 알림을 통째로 보내, 사용자는 "마케팅은 싫고 주문 알림만 받고 싶어도" 앱 알림을 전부 꺼야 했다. 채널은 알림을 "주문 알림"·"마케팅"·"채팅"처럼 종류로 묶어, 사용자가 채널 단위로 끄거나 소리·중요도를 조절하게 한다 — 앱이 아니라 사용자가 세밀한 통제권을 쥔다.
// 채널 생성 (앱 시작 시 1회) — 종류별로 만든다
val channel = NotificationChannel("orders", "주문 알림", NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(channel)
// 알림 표시
val notif = NotificationCompat.Builder(context, "orders") // 어느 채널인지 지정
.setSmallIcon(R.drawable.ic_noti)
.setContentTitle("배송 시작").setContentText("주문 #42가 출발했어요")
.setContentIntent(pendingIntent) // 탭하면 열릴 화면(딥링크)
.addAction(R.drawable.ic_track, "추적", trackIntent)
.build()
notificationManager.notify(42, notif)
- 중요도(importance) — 채널의 소리·헤드업(화면 위로 튀어나옴) 수준. 한 번 정하면 사용자가 조절할 수 있어, 처음 값을 신중히 정한다(채팅은 높게, 마케팅은 낮게).
PendingIntent— 탭 시 동작(특정 화면 열기). immutable 플래그를 권장한다(보안).- 스타일 —
BigTextStyle(긴 글)·BigPictureStyle(이미지)·MessagingStyle(채팅 대화).
POST_NOTIFICATIONS — 런타임 권한 (Android 13+)
Android 13+부터 알림을 띄우는 것 자체가 런타임 권한(POST_NOTIFICATIONS)이 됐다 — 사용자가 "허용"을 눌러야 알림이 뜬다([10]의 권한 흐름). 그래서 왜 알림이 필요한지 맥락을 설명하고 적절한 시점에 요청해야 한다(앱 첫 실행에 다짜고짜 묻기보다, 알림이 가치 있는 순간에).
FCM — 서버가 앱을 깨운다
앱이 꺼져 있어도 서버가 새 소식을 보내려면 — 직접 연결을 유지할 수 없다(배터리). 대신 구글의 푸시 인프라인 **Firebase Cloud Messaging(FCM)**을 거친다. 흐름은 토큰을 중심으로 돈다.
class MyFcmService : FirebaseMessagingService() {
override fun onNewToken(token: String) { sendTokenToServer(token) } // ★ 토큰 등록/갱신
override fun onMessageReceived(msg: RemoteMessage) {
// 알림 표시 또는 데이터 동기화(길면 WorkManager로 넘김)
}
}
- 토큰(token) — 기기별 주소다. 앱이 발급받아 서버에 등록하고, 토큰이 갱신(
onNewToken)되면 서버를 업데이트해야 한다(안 하면 푸시가 안 닿는다). - 메시지 종류 — *알림 메시지(notification)*는 앱이 백그라운드일 때 시스템이 자동으로 알림을 표시하고, *데이터 메시지(data)*는
onMessageReceived에서 직접 처리한다. 표시 방식·로직을 내가 제어하려면 데이터 메시지를 쓴다. - 토픽(topic) — *"news를 구독한 모두"*에게 일괄 발송(개별 토큰 없이).
절제·정확성
- 과한 알림은 차단·삭제로 직결된다 — 사용자에게 진짜 가치 있는 것만 보낸다.
onMessageReceived의 백그라운드 처리는 짧게 — 길면 WorkManager([07])로 넘긴다(여기서 오래 끌면 제약·ANR).- 딥링크([08])로 알림을 탭하면 정확히 그 콘텐츠 화면으로 보낸다(목록이 아니라 해당 주문 상세로).
정리 — 채널로 통제권을, FCM으로 서버 푸시를
알림의 핵심 — 채널로 종류별 사용자 통제(중요도·끄기), NotificationCompat+PendingIntent로 표시·탭 동작·스타일, Android 13+는 POST_NOTIFICATIONS 런타임 권한(맥락 있게 요청), FCM으로 서버 푸시(토큰 등록·갱신·알림 vs 데이터 메시지·토픽), 긴 처리는 WorkManager·탭은 딥링크·무엇보다 절제. 사용자가 통제권을 쥔 환경에서 가치 있는 알림만 보내는 게 차단을 피하는 길이다.
한 줄 요약 — 알림=사용자 주의를 빌리는 일(남용→차단). 채널(Android 8+·종류별 사용자 통제·중요도)+
NotificationCompat+PendingIntent(탭=딥링크)+스타일. Android 13+POST_NOTIFICATIONS런타임 권한. 푸시는 FCM: 토큰 발급·서버 등록·갱신(onNewToken)·onMessageReceived·알림 vs 데이터 메시지(제어는 데이터)·토픽(일괄). 긴 처리는 WorkManager·절제 필수.
(출처: Android — Notifications overview / Notification runtime permission / Firebase Cloud Messaging / FCM docs.)