Android 알림(Notification) 완벽 가이드 — 권한 처리부터 FCM 푸시 알림과 WorkManager까지
Android 앱에서 알림은 사용자에게 중요한 정보를 전달하는 기능입니다.
예를 들어 다음과 같은 상황에서 활용할 수 있습니다.
사용자가 등록한 일정이나 기념일 안내
데이터 동기화 완료 알림
새로운 콘텐츠 등록 안내
서버에서 발생한 중요한 이벤트 전달
백그라운드 작업 결과 안내
사용자가 설정한 특정 조건의 알림
하지만 Android 알림은 단순히 NotificationManager를 호출한다고 모든 기기에서 정상적으로 표시되는 것은 아닙니다.
Android 버전에 따라 알림 권한과 채널 설정이 다르고, 백그라운드 실행 제한이나 배터리 최적화 정책도 고려해야 합니다.
특히 Android 13부터는 일반적인 알림을 표시하기 위해 런타임 권한 요청이 필요해졌습니다.
이번 글에서는 Kotlin 기반 Android 앱에서 로컬 알림과 FCM 푸시 알림을 구현하고, WorkManager와 연결하는 방법까지 알아보겠습니다.
작성 기준: 2026년 10월. 예제는 Kotlin + XML, AndroidX 및 Firebase 기반입니다. 라이브러리 버전과 Android 정책은 변경될 수 있으며, 코드는 프로젝트에 맞게 조정해야 하는 설명용 예제입니다.
1. Android 알림의 종류
Android 앱에서 사용하는 알림은 크게 로컬 알림과 푸시 알림으로 나눌 수 있습니다.
| 구분 | 로컬 알림 | 푸시 알림 |
|---|---|---|
| 발생 위치 | 사용자 기기 | 서버 또는 Firebase |
| 인터넷 연결 | 알림 생성 자체에는 불필요 | 전달에 필요 |
| 대표적인 용도 | 일정 알림, 작업 완료 | 새 소식, 서버 이벤트 |
| 주요 API | NotificationManager | Firebase Cloud Messaging |
| 예약 실행 | WorkManager, AlarmManager 등 | 서버 예약 처리 |
| 백그라운드 고려 | 필요 | 필요 |
로컬 알림은 앱이 기기 내부에서 직접 생성합니다.
푸시 알림은 서버가 FCM 같은 서비스를 통해 기기로 메시지를 전달하는 방식입니다.
중요한 점은 푸시 메시지를 수신했다고 해서 항상 사용자가 볼 수 있는 알림이 표시되는 것은 아니라는 것입니다.
알림 권한, 채널 설정, 앱 상태, 메시지 유형 등에 따라 동작이 달라집니다.
2. Android 버전별 알림 관련 변경사항
| Android 버전 | 주요 특징 |
|---|---|
| Android 7 이하 | 일반적인 알림 API 사용 |
| Android 8.0(API 26) | NotificationChannel 도입 |
| Android 12(API 31) | PendingIntent 변경 가능성 지정 요구 |
| Android 13(API 33) | POST_NOTIFICATIONS 런타임 권한 도입 |
| Android 14 이상 | 백그라운드 작업 및 전체 화면 알림 등 관련 제한 고려 |
| Android 15·16 | 기존 알림·백그라운드 실행 정책과 추가 플랫폼 변경사항 확인 필요 |
알림을 구현할 때는 minSdk뿐 아니라 targetSdk에 따른 동작 차이도 확인해야 합니다.
특히 Android 13 이상의 알림 권한은 기존 앱을 업데이트하는 경우에도 중요한 부분입니다.
3. AndroidManifest.xml 설정
먼저 알림 권한을 선언합니다.
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS" />이 권한은 Android 13(API 33) 이상에서 일반적인 알림을 표시할 때 사용됩니다.
Android 12 이하에서는 동일한 런타임 권한 요청이 필요하지 않습니다.
FCM을 사용할 예정이라면 인터넷 권한도 필요합니다.
<uses-permission
android:name="android.permission.INTERNET" />단, 알림을 표시하기 위한 모든 경우에 인터넷 권한이 필요한 것은 아닙니다. 로컬 알림만 사용하는 앱은 인터넷 연결 없이도 알림을 생성할 수 있습니다.
4. Android 13 알림 권한 요청하기
Android 13 이상에서는 알림 권한을 사용자에게 요청해야 합니다.
Kotlin에서는 Activity Result API를 활용할 수 있습니다.
private val notificationPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) {
// 알림 사용 가능
} else {
// 알림이 허용되지 않은 상태
}
}권한 요청 함수는 다음과 같습니다.
private fun requestNotificationPermission() {
if (
Build.VERSION.SDK_INT >=
Build.VERSION_CODES.TIRAMISU
) {
val permission =
Manifest.permission.POST_NOTIFICATIONS
if (
ContextCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED
) {
notificationPermissionLauncher.launch(
permission
)
}
}
}사용자가 알림 기능을 활성화하거나 관련 설정을 변경할 때 권한을 요청하는 것이 좋습니다.
앱을 실행하자마자 아무 설명 없이 권한 요청 창을 표시하는 방식은 사용자 경험 측면에서 권장하지 않습니다.
5. 사용자가 알림 권한을 거부한 경우
사용자가 알림 권한을 거부했다고 해서 앱의 주요 기능이 동작하지 않도록 만들어서는 안 됩니다.
예를 들어 기념일 관리 앱이라면 알림 권한이 없어도 기념일 등록과 조회 기능은 정상적으로 사용할 수 있어야 합니다.
권한이 없는 경우에는 다음과 같이 안내할 수 있습니다.
알림 권한이 꺼져 있어 기념일 알림을 받을 수 없습니다. 기기 설정에서 알림을 허용할 수 있습니다.
앱 알림 설정 화면으로 이동하려면 다음과 같은 Intent를 사용할 수 있습니다.
val intent = Intent(
Settings.ACTION_APP_NOTIFICATION_SETTINGS
).apply {
putExtra(
Settings.EXTRA_APP_PACKAGE,
packageName
)
}
startActivity(intent)사용자를 설정 화면으로 이동시킬 수는 있지만, 앱이 사용자의 알림 권한을 임의로 활성화할 수는 없습니다.
6. NotificationChannel이란?
Android 8.0(API 26)부터는 알림을 표시할 때 NotificationChannel이 필요합니다.
알림 채널은 알림의 종류를 구분하고 사용자가 알림 동작을 설정할 수 있도록 하는 기능입니다.
예를 들어 다음과 같이 채널을 구분할 수 있습니다.
| 채널 ID | 용도 |
|---|---|
general | 일반 안내 |
reminder | 일정 및 기념일 |
sync | 데이터 동기화 결과 |
updates | 새로운 콘텐츠 안내 |
채널별로 중요도, 소리, 진동 등의 설정을 관리할 수 있습니다.
단, 채널을 생성한 이후에는 앱이 일부 동작 설정을 자유롭게 변경할 수 없습니다.
사용자가 직접 채널 설정을 변경할 수도 있기 때문입니다.
7. NotificationChannel 생성하기
다음은 일반 알림 채널을 생성하는 예제입니다.
object NotificationChannels {
const val GENERAL = "general"
const val REMINDER = "reminder"
fun create(context: Context) {
if (
Build.VERSION.SDK_INT <
Build.VERSION_CODES.O
) {
return
}
val manager =
context.getSystemService(
NotificationManager::class.java
)
val generalChannel =
NotificationChannel(
GENERAL,
"일반 알림",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "앱의 일반적인 안내 알림"
}
val reminderChannel =
NotificationChannel(
REMINDER,
"일정 알림",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "일정 및 기념일 알림"
}
manager.createNotificationChannels(
listOf(
generalChannel,
reminderChannel
)
)
}
}채널 ID는 앱 내부에서 안정적으로 유지해야 합니다.
기존 채널의 ID를 임의로 변경하면 사용자가 설정한 알림 환경이 예상과 다르게 동작할 수 있습니다.
8. Application에서 채널 초기화하기
알림 채널은 앱 시작 시 생성하도록 구성할 수 있습니다.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
NotificationChannels.create(this)
}
}AndroidManifest.xml에도 Application 클래스를 등록해야 합니다.
<application
android:name=".MyApplication"
... >
</application>이미 동일한 ID의 채널이 존재하면 새 채널을 중복으로 만드는 것이 아니라 기존 채널이 유지됩니다.
9. 가장 기본적인 로컬 알림 만들기
로컬 알림은 NotificationCompat.Builder를 이용해 생성할 수 있습니다.
fun showNotification(
context: Context
) {
val notification =
NotificationCompat.Builder(
context,
NotificationChannels.GENERAL
)
.setSmallIcon(
R.drawable.ic_notification
)
.setContentTitle(
"알림 테스트"
)
.setContentText(
"새로운 알림이 도착했습니다."
)
.setPriority(
NotificationCompat.PRIORITY_DEFAULT
)
.setAutoCancel(true)
.build()
val manager =
NotificationManagerCompat.from(context)
if (
ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED ||
Build.VERSION.SDK_INT <
Build.VERSION_CODES.TIRAMISU
) {
manager.notify(
1001,
notification
)
}
}ic_notification은 프로젝트에서 직접 준비한 알림용 아이콘 리소스입니다.
Android 알림의 작은 아이콘은 일반적인 앱 런처 아이콘과 표시 방식이 다르므로 별도의 단색 아이콘을 사용하는 것이 좋습니다.
또한 이 코드에서는 알림 권한만 확인하지만, 사용자가 앱 전체 알림이나 특정 채널을 차단한 경우에도 알림이 표시되지 않을 수 있습니다.
10. 알림 ID 관리하기
notify()에 전달하는 숫자는 알림 ID입니다.
manager.notify(
1001,
notification
)같은 ID로 알림을 다시 표시하면 기존 알림이 업데이트되거나 대체될 수 있습니다.
반대로 다른 ID를 사용하면 별도의 알림으로 표시할 수 있습니다.
예를 들어 다음과 같이 구분할 수 있습니다.
object NotificationIds {
const val GENERAL = 1000
const val SYNC_COMPLETE = 2000
const val REMINDER_BASE = 3000
}기념일이나 일정별 알림은 고유한 ID를 부여하는 것이 좋습니다.
단, 무조건 현재 시간을 ID로 사용하는 방식은 동일한 이벤트의 알림이 중복 생성되는 문제를 일으킬 수 있습니다.
11. PendingIntent로 알림 클릭 처리하기
사용자가 알림을 누르면 특정 Activity로 이동하도록 만들 수 있습니다.
이때 사용하는 것이 PendingIntent입니다.
val intent = Intent(
context,
MainActivity::class.java
).apply {
flags =
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra(
"destination",
"reminder"
)
}
val pendingIntent =
PendingIntent.getActivity(
context,
1001,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_IMMUTABLE
)알림에 PendingIntent를 연결합니다.
val notification =
NotificationCompat.Builder(
context,
NotificationChannels.REMINDER
)
.setSmallIcon(
R.drawable.ic_notification
)
.setContentTitle(
"기념일 알림"
)
.setContentText(
"오늘 등록한 기념일이 있습니다."
)
.setContentIntent(
pendingIntent
)
.setAutoCancel(true)
.build()Android 12 이상을 대상으로 하는 앱에서는 PendingIntent의 변경 가능성을 명시해야 합니다.
일반적인 알림 클릭 용도에서는 FLAG_IMMUTABLE을 우선 고려하는 것이 좋습니다.
12. 알림 클릭 시 특정 상세 화면 열기
알림을 누르면 목록이 아니라 특정 데이터의 상세 화면으로 이동해야 하는 경우가 있습니다.
예를 들어 다음과 같은 상황입니다.
기념일 알림
│
▼
MainActivity
│
▼
기념일 상세 화면Intent에 데이터 ID를 전달할 수 있습니다.
val intent = Intent(
context,
MainActivity::class.java
).apply {
putExtra(
"reminder_id",
reminderId
)
}Activity에서는 다음과 같이 값을 가져올 수 있습니다.
val reminderId =
intent.getLongExtra(
"reminder_id",
-1L
)BottomNavigation과 NavController를 사용하는 앱이라면 해당 ID를 기반으로 상세 화면으로 이동할 수 있습니다.
다만 Activity가 이미 실행 중인 경우에는 onCreate()가 아니라 onNewIntent()로 새로운 Intent가 전달될 수 있습니다.
따라서 두 진입 경로를 모두 고려해야 합니다.
또한 여러 알림이 서로 다른 상세 데이터를 가리키는 경우 PendingIntent의 식별 방식에 주의해야 합니다.
Intent의 extras만 다른 PendingIntent는 동일한 것으로 취급될 수 있으므로 요청 코드나 데이터 URI 등을 이용해 이벤트별로 구분하는 것이 좋습니다.
13. Firebase Cloud Messaging이란?
FCM은 Firebase에서 제공하는 메시지 전달 서비스입니다.
서버에서 Android 앱으로 메시지를 전송할 때 사용할 수 있습니다.
기본 구조는 다음과 같습니다.
Spring Boot 서버
│
▼
Firebase Cloud Messaging
│
▼
Android 기기
│
▼
앱 또는 시스템 알림예를 들어 서버에 새로운 콘텐츠가 등록되었을 때 해당 정보를 사용자에게 전달할 수 있습니다.
다만 FCM은 메시지의 즉시 전달이나 모든 메시지의 전달을 보장하는 시스템은 아닙니다.
기기의 네트워크 상태, 배터리 관리, 메시지 우선순위, 앱 상태 등에 따라 전달 시점이 달라질 수 있습니다.
14. Firebase 프로젝트 연결하기
FCM을 사용하려면 Firebase 프로젝트와 Android 앱을 연결해야 합니다.
일반적인 설정 순서는 다음과 같습니다.
Firebase Console에서 프로젝트 생성
Android 앱 등록
google-services.json다운로드앱 모듈에 파일 추가
Google Services Gradle 플러그인 적용
Firebase Messaging 의존성 추가
Firebase Console:
https://console.firebase.google.com/
프로젝트의 Application ID와 Firebase에 등록된 패키지 이름이 일치해야 합니다.
Product Flavor별 Application ID가 다르다면 각 앱을 Firebase에 적절히 등록해야 합니다.
15. Firebase Messaging 의존성 추가
앱 모듈의 build.gradle.kts에 Firebase Messaging을 추가합니다.
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.gms.google-services")
}Firebase BoM을 사용하는 예제입니다.
dependencies {
implementation(
platform(
"com.google.firebase:firebase-bom:<version>"
)
)
implementation(
"com.google.firebase:firebase-messaging"
)
}<version>은 실제 프로젝트에서 사용하는 Firebase SDK와 호환되는 버전을 지정해야 합니다.
Google Services 플러그인도 별도로 버전을 선언해야 하며, Firebase BoM이 Gradle 플러그인 버전까지 관리하는 것은 아닙니다.
16. FCM 등록 토큰 가져오기
FCM에서는 기기 앱 인스턴스에 대한 등록 토큰을 사용할 수 있습니다.
FirebaseMessaging.getInstance()
.token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(
"FCM",
"토큰 조회 실패",
task.exception
)
return@addOnCompleteListener
}
val token = task.result
Log.d(
"FCM",
"토큰 조회 성공"
)
// 서버에 토큰 등록
}실제 서비스에서는 등록 토큰 전체를 Logcat이나 Crashlytics에 출력하지 않는 것이 좋습니다.
토큰은 특정 앱 인스턴스에 메시지를 전달하는 데 사용되는 값이므로 안전하게 관리해야 합니다.
또한 토큰은 변경될 수 있습니다.
따라서 앱을 설치할 때 한 번만 가져오는 방식으로 구현해서는 안 됩니다.
17. FirebaseMessagingService 구현하기
FCM 메시지를 처리하려면 FirebaseMessagingService를 구현할 수 있습니다.
class MyFirebaseMessagingService :
FirebaseMessagingService() {
override fun onNewToken(
token: String
) {
super.onNewToken(token)
// 서버에 변경된 토큰 등록
}
override fun onMessageReceived(
message: RemoteMessage
) {
super.onMessageReceived(message)
val title =
message.notification?.title
?: "새로운 알림"
val body =
message.notification?.body
?: "새로운 메시지가 도착했습니다."
// 메시지 유형과 앱 상태에 따라
// 알림 생성 또는 데이터 처리
}
}onNewToken()은 등록 토큰이 새로 생성되거나 변경될 때 호출될 수 있습니다.
서버에 토큰을 저장하는 앱이라면 변경된 토큰을 반영하는 구조가 필요합니다.
18. AndroidManifest에 FCM 서비스 등록
다음과 같이 서비스를 등록합니다.
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action
android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>Firebase Messaging SDK가 제공하는 기능과 Manifest 병합 결과도 확인해야 합니다.
특히 여러 Firebase 라이브러리를 사용하는 경우에는 중복 선언이나 설정 충돌이 없는지 확인하는 것이 좋습니다.
19. FCM Notification 메시지와 Data 메시지 차이
FCM 메시지는 크게 알림 메시지와 데이터 메시지로 구분할 수 있습니다.
| 구분 | Notification 메시지 | Data 메시지 |
|---|---|---|
| 주요 용도 | 사용자에게 표시할 알림 | 앱에서 처리할 데이터 |
| 포그라운드 | 앱 콜백에서 처리 가능 | 앱 콜백에서 처리 가능 |
| 백그라운드 | 시스템이 알림을 표시할 수 있음 | 앱 콜백 처리 대상 |
| 처리 방식 | 앱 상태에 따라 달라짐 | 앱 로직 중심 |
| 주의사항 | 중복 알림 가능성 | 실행 시간·전달 제한 |
예를 들어 Notification 메시지는 다음과 같은 형태로 전송할 수 있습니다.
{
"message": {
"token": "DEVICE_FCM_TOKEN",
"notification": {
"title": "새로운 소식",
"body": "새로운 콘텐츠가 등록되었습니다."
}
}
}반면 Data 메시지는 다음과 같습니다.
{
"message": {
"token": "DEVICE_FCM_TOKEN",
"data": {
"type": "content_update",
"content_id": "123"
}
}
}위 JSON은 FCM HTTP v1 메시지 구조를 설명하기 위한 예시입니다.
실제 전송에는 적절한 서버 인증과 API 호출이 필요합니다.
20. 백그라운드에서 onMessageReceived가 호출되지 않는 이유
FCM을 구현하다 보면 다음과 같은 상황을 경험할 수 있습니다.
앱을 실행 중일 때는 onMessageReceived가 호출되는데, 앱을 백그라운드로 보내면 호출되지 않습니다.
이는 반드시 오류를 의미하지 않습니다.
Notification 메시지는 앱이 백그라운드에 있을 때 시스템이 알림을 처리할 수 있습니다.
반면 Data 메시지는 앱 콜백을 통해 처리하는 방식이지만, 기기 상태와 시스템 제한에 영향을 받을 수 있습니다.
따라서 모든 FCM 메시지가 항상 onMessageReceived()에 도착한다고 가정해서는 안 됩니다.
또한 onMessageReceived()는 긴 작업을 실행하기 위한 메서드가 아닙니다.
서버에서 추가 데이터를 다운로드하거나 데이터베이스에 대량의 데이터를 저장해야 한다면 WorkManager 등으로 작업을 분리하는 것이 좋습니다.
21. FCM 중복 알림 방지하기
FCM을 구현할 때 흔히 발생하는 문제 중 하나는 동일한 알림이 두 번 표시되는 것입니다.
대표적인 원인은 다음과 같습니다.
시스템이 Notification 메시지를 자동 표시함
앱에서도 동일한 알림을 직접 생성함
서버에서 동일한 메시지를 중복 전송함
메시지 재처리 시 알림을 다시 생성함
이를 방지하려면 메시지 처리 정책을 명확하게 정해야 합니다.
예를 들어 다음과 같이 구성할 수 있습니다.
Notification 메시지
└── 시스템 표시 방식 활용
Data 메시지
└── 앱에서 조건 확인 후 알림 생성단, 앱 상태와 FCM 메시지 유형에 따라 처리 방식이 달라지므로 모든 상황을 하나의 규칙으로 단순화해서는 안 됩니다.
서버에서 메시지 ID를 발급하고 앱에서 처리 여부를 관리하면 중복 처리를 줄이는 데 도움이 됩니다.
22. FCM 토큰을 Spring Boot 서버에 저장하기
서버에서 특정 사용자에게 푸시 메시지를 전송하려면 FCM 등록 토큰을 관리해야 합니다.
예를 들어 다음과 같은 테이블을 사용할 수 있습니다.
CREATE TABLE fcm_tokens (
id BIGINT PRIMARY KEY,
user_id BIGINT,
token VARCHAR(512) NOT NULL,
updated_at TIMESTAMP NOT NULL
);실제 운영에서는 토큰의 유일성, 사용자와 기기 간 관계, 토큰 갱신, 만료 및 삭제 정책을 함께 설계해야 합니다.
한 명의 사용자가 여러 기기를 사용하는 경우에는 여러 토큰이 필요할 수 있습니다.
또한 사용자가 로그아웃하면 해당 기기로 개인화된 알림이 전송되지 않도록 서버의 토큰 연결을 해제하는 것이 좋습니다.
FCM 토큰 자체를 사용자 인증 수단으로 사용해서는 안 됩니다.
23. Spring Boot에서 FCM 전송하기
Spring Boot 서버에서는 Firebase Admin SDK를 이용해 FCM 메시지를 전송할 수 있습니다.
예를 들어 다음과 같은 구조입니다.
Android 앱
│
└── FCM 토큰 등록
│
▼
Spring Boot
│
└── 토큰 저장
│
▼
알림 이벤트
│
▼
Firebase Admin
│
▼
FCM
│
▼
Android 앱Java 기반 Spring Boot에서는 Firebase Admin SDK를 사용할 수 있습니다.
의존성은 프로젝트의 빌드 시스템에 맞춰 추가합니다.
Gradle 예시:
dependencies {
implementation(
"com.google.firebase:firebase-admin:<version>"
)
}Firebase 서비스 계정 인증 정보를 서버에 안전하게 구성해야 합니다.
서비스 계정 JSON 파일을 Android 앱이나 공개 Git 저장소에 포함해서는 안 됩니다.
24. Firebase Admin SDK 메시지 전송 예제
다음은 Spring Boot 서버에서 메시지를 전송하는 기본적인 Java 예제입니다.
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
public class FcmSender {
public String send(
String token,
String title,
String body
) throws Exception {
Message message = Message.builder()
.setToken(token)
.setNotification(
Notification.builder()
.setTitle(title)
.setBody(body)
.build()
)
.build();
return FirebaseMessaging
.getInstance()
.send(message);
}
}이 코드는 Firebase Admin SDK가 정상적으로 초기화되어 있다는 전제에서 동작합니다.
실제 운영에서는 전송 실패 처리, 토큰 무효화, 재시도 정책, 전송 속도 제한 등을 추가해야 합니다.
특히 인증 실패나 무효 토큰 오류를 무조건 재시도해서는 안 됩니다.
25. WorkManager로 예약 알림 구현하기
로컬 알림을 예약해야 하는 경우 WorkManager를 사용할 수 있습니다.
예를 들어 다음과 같은 상황입니다.
일정 시간이 지난 뒤 안내
백그라운드 데이터 동기화 후 알림
주기적인 작업 결과 안내
다만 WorkManager는 정확한 시각에 작업을 실행하도록 보장하는 도구가 아닙니다.
시스템의 배터리 최적화와 작업 스케줄링에 따라 실행 시점이 지연될 수 있습니다.
정확한 시각이 중요한 알람이나 일정 알림이라면 AlarmManager와 정확한 알람 관련 정책을 검토해야 합니다.
26. 알림용 Worker 작성하기
다음은 Worker에서 알림을 생성하는 예제입니다.
class ReminderWorker(
context: Context,
workerParams: WorkerParameters
) : Worker(
context,
workerParams
) {
override fun doWork(): Result {
val title =
inputData.getString("title")
?: "일정 알림"
val message =
inputData.getString("message")
?: "등록한 일정이 있습니다."
NotificationHelper.show(
applicationContext,
title,
message
)
return Result.success()
}
}여기서 NotificationHelper.show()는 앞서 설명한 NotificationCompat 기반 알림 생성 기능을 별도 클래스로 분리한 함수라고 가정합니다.
실제 프로젝트에서는 알림 권한이 없는 경우도 정상적으로 처리해야 합니다.
또한 알림이 표시되지 않았다는 이유만으로 무조건 Worker를 재시도하면 중복 실행 문제가 발생할 수 있습니다.
27. OneTimeWorkRequest로 알림 예약하기
예를 들어 30분 후 알림을 요청할 수 있습니다.
val inputData = workDataOf(
"title" to "일정 안내",
"message" to "확인할 일정이 있습니다."
)
val request =
OneTimeWorkRequestBuilder<
ReminderWorker
>()
.setInitialDelay(
30,
TimeUnit.MINUTES
)
.setInputData(
inputData
)
.build()
WorkManager.getInstance(
context
).enqueue(request)이 코드는 약 30분 이후 실행 가능한 작업을 예약하는 예제입니다.
정확히 30분 후에 실행된다고 보장하지는 않습니다.
28. 동일한 예약 작업 중복 방지하기
같은 일정에 대한 알림이 여러 번 등록되지 않도록 하려면 Unique Work를 사용할 수 있습니다.
WorkManager.getInstance(
context
).enqueueUniqueWork(
"reminder_$reminderId",
ExistingWorkPolicy.REPLACE,
request
)이렇게 하면 같은 이름의 기존 작업을 새 작업으로 교체할 수 있습니다.
다만 REPLACE는 기존 작업을 취소하고 새 작업을 등록하는 방식이므로, 이미 실행 중인 작업과의 관계를 고려해야 합니다.
일정이 삭제되었을 때는 해당 예약 작업을 취소하는 처리도 필요합니다.
29. WorkManager와 AlarmManager 비교
| 항목 | WorkManager | AlarmManager |
|---|---|---|
| 주된 목적 | 지연 가능한 백그라운드 작업 | 특정 시점의 알람 |
| 정확한 실행 시각 | 보장하지 않음 | 정확한 알람 API 사용 시 가능 |
| 시스템 최적화 | 작업 스케줄링에 영향받음 | 알람 유형과 권한에 따라 다름 |
| 재시도 처리 | 지원 | 직접 설계 필요 |
| 작업 조건 | 네트워크 등 제약조건 지원 | WorkManager와 다른 방식 |
| 추천 용도 | 동기화, 지연 가능한 알림 | 정확한 시각의 일정 알림 |
예를 들어 음력달력이나 기념일 앱에서 사용자가 오전 9시에 알림을 받도록 설정했다면 WorkManager만으로 정확한 시각을 보장하기 어렵습니다.
이 경우 AlarmManager를 검토해야 합니다.
다만 Android 12 이상에서는 정확한 알람 사용에 추가적인 제한과 권한 정책이 적용될 수 있습니다.
따라서 정확한 알람이 정말 필요한 앱인지 먼저 판단해야 합니다.
30. 기념일 알림에서 고려해야 할 문제
기념일 알림은 단순히 하루 뒤 알림을 예약하는 것보다 복잡합니다.
예를 들어 다음과 같은 상황을 고려해야 합니다.
매년 반복되는 생일
음력 날짜 변환
윤달 여부
사용자의 시간대 변경
기기 재부팅
앱 업데이트
사용자가 알림 시간을 변경
알림 권한 거부
배터리 최적화
앱 데이터 초기화
특히 음력 기념일은 매년 양력 날짜가 달라질 수 있으므로 단순히 365일 간격으로 예약해서는 안 됩니다.
해당 연도의 음력 날짜를 양력으로 변환한 뒤 다음 알림 시각을 계산하는 방식이 필요합니다.
31. 알림을 너무 많이 보내지 않기
알림은 사용자가 앱을 다시 실행하도록 유도할 수 있지만 지나치게 많이 보내면 오히려 불편함을 줄 수 있습니다.
예를 들어 다음과 같은 알림은 주의해야 합니다.
사용자가 요청하지 않은 반복적인 홍보 알림
같은 내용을 짧은 시간에 여러 번 전송
앱을 실행하라는 목적만 있는 불필요한 알림
중요한 알림과 일반 안내를 동일한 채널로 처리
가능하면 알림의 목적에 따라 채널을 분리하고 사용자가 필요한 알림만 받을 수 있도록 설정을 제공하는 것이 좋습니다.
또한 마케팅 목적의 알림은 서비스 운영 지역의 관련 법규와 동의 요건을 별도로 검토해야 합니다.
32. 알림이 표시되지 않을 때 점검할 사항
알림이 표시되지 않는 경우 다음 항목을 확인할 수 있습니다.
| 확인 항목 | 점검 내용 |
|---|---|
| 알림 권한 | Android 13 이상에서 허용되었는가 |
| 앱 알림 설정 | 앱 전체 알림이 차단되지 않았는가 |
| 채널 설정 | 해당 NotificationChannel이 차단되지 않았는가 |
| 채널 ID | Builder에 올바른 ID를 사용했는가 |
| 알림 아이콘 | 유효한 Small Icon이 설정되었는가 |
| FCM 토큰 | 현재 앱 인스턴스의 토큰인가 |
| Firebase 설정 | Application ID와 프로젝트가 일치하는가 |
| 앱 상태 | 포그라운드·백그라운드 동작 차이를 고려했는가 |
| 백그라운드 제한 | 기기 및 시스템 제한의 영향을 받는가 |
| 예약 방식 | 정확한 실행 시각이 필요한 작업인가 |
FCM 메시지를 테스트할 때는 포그라운드, 백그라운드, 앱 프로세스가 없는 상태를 구분해 확인하는 것이 좋습니다.
단, 사용자가 앱을 강제 종료한 경우 등에는 메시지 전달과 실행 동작에 추가적인 제한이 있을 수 있습니다.
33. 개인 개발자에게 추천하는 알림 구조
Kotlin + MVVM 기반 Android 앱이라면 알림 기능을 다음과 같이 분리하는 것이 좋습니다.
Activity / Fragment
│
▼
ViewModel
│
▼
Notification Repository
│
├── NotificationHelper
│ └── 로컬 알림 표시
│
├── ReminderScheduler
│ └── WorkManager / AlarmManager
│
└── FCM Token Repository
└── 서버 토큰 등록이렇게 하면 화면 코드와 알림 생성 로직을 분리할 수 있습니다.
또한 FCM 메시지 처리, 로컬 알림, 예약 알림을 각각 테스트하기 쉬워집니다.
알림 클릭 시 이동할 화면이나 데이터 ID를 관리하는 로직도 별도 계층으로 분리할 수 있습니다.
34. Android 알림 구현 체크리스트
AndroidManifest 알림 권한 선언
Android 13 이상 런타임 권한 요청
권한 거부 시 정상적인 앱 동작 유지
NotificationChannel 생성
알림용 Small Icon 준비
NotificationCompat 기반 알림 생성
알림 ID 관리
PendingIntent 변경 가능성 지정
알림별 고유 PendingIntent 구분
알림 클릭 시 상세 화면 이동
Firebase 프로젝트 연결
FCM 토큰 조회 및 갱신
FirebaseMessagingService 등록
Notification·Data 메시지 차이 이해
FCM 중복 알림 방지
서버 토큰 저장 및 삭제 정책
WorkManager 예약 작업 구현
정확한 알람 필요 여부 검토
기기 재부팅 및 시간대 변경 고려
알림 채널별 사용자 설정 제공
포그라운드·백그라운드 테스트
개인정보 및 알림 정책 검토
35. 마무리
Android 알림 기능은 단순히 메시지를 표시하는 기능이 아닙니다.
Android 버전별 권한 정책, 알림 채널, 앱 상태, 백그라운드 실행 제한, 서버 메시지 전달 방식 등을 함께 고려해야 합니다.
특히 실무에서는 다음 원칙을 추천합니다.
Android 13 이상에서는 알림 권한을 적절한 시점에 요청합니다.
Android 8.0 이상에서는 NotificationChannel을 사용합니다.
PendingIntent로 알림 클릭 동작을 명확하게 구성합니다.
FCM Notification 메시지와 Data 메시지의 차이를 이해합니다.
FCM 토큰은 변경될 수 있으므로 서버에서 갱신·관리합니다.
WorkManager는 정확한 시각을 보장하지 않는다는 점을 이해합니다.
정확한 일정 알림이 필요하면 AlarmManager와 관련 정책을 검토합니다.
사용자가 원하는 알림만 받을 수 있도록 설정을 제공합니다.
개인 개발자가 운영하는 앱이라면 처음부터 복잡한 푸시 시스템을 구축할 필요는 없습니다.
로컬 알림과 권한 처리부터 안정적으로 구현한 뒤, 서버에서 전달할 정보가 필요한 경우 FCM을 추가하는 것이 현실적입니다.
Android 알림 기능의 핵심은 알림을 많이 보내는 것이 아니라, 사용자에게 필요한 정보를 적절한 시점에 안정적으로 전달하는 것입니다.
참고 자료
Android Developers — 알림 개요
https://developer.android.com/develop/ui/views/notificationsAndroid Developers — 알림 채널 생성
https://developer.android.com/develop/ui/views/notifications/channelsAndroid Developers — 알림 런타임 권한
https://developer.android.com/develop/ui/views/notifications/notification-permissionAndroid Developers — PendingIntent
https://developer.android.com/reference/android/app/PendingIntentFirebase Cloud Messaging 공식 문서
https://firebase.google.com/docs/cloud-messagingFirebase Cloud Messaging Android 클라이언트 설정
https://firebase.google.com/docs/cloud-messaging/android/clientFirebase Admin SDK 메시지 전송
https://firebase.google.com/docs/cloud-messaging/send/admin-sdkAndroid Developers — WorkManager
https://developer.android.com/topic/libraries/architecture/workmanagerAndroid Developers — AlarmManager
https://developer.android.com/develop/background-work/services/alarms
태그: Android, Kotlin, Notification, NotificationChannel, POST_NOTIFICATIONS, PendingIntent, Firebase, FCM, FirebaseMessagingService, WorkManager, AlarmManager, Spring Boot, Android 13, 푸시 알림, 로컬 알림