Firebase Cloud Messaging(FCM)으로 안드로이드 앱에 푸시 알림 보내기

By | 2026년 5월 6일
Table of Contents

Firebase Cloud Messaging(FCM)으로 안드로이드 앱에 푸시 알림 보내기

Firebase Cloud Messaging(FCM)을 이용해 특정 토픽(Topic)을 구독한 안드로이드 기기에 푸시 알림을 전송하는 방법을 단계별로 정리합니다.

모든 기능이 나열되어 있지 않습니다.
수신된 푸시 로컬 저장, 생명주기에 따른 메모리 누수 방지 코드 등 필요한 코드를 추가해야 정상작동합니다.


1. Firebase 프로젝트 설정

1-1. Firebase 프로젝트 생성

  1. Firebase 콘솔에 접속합니다.
  2. 새 Firebase 프로젝트 만들기 버튼을 클릭합니다.
  3. 프로젝트 이름을 입력하고 Google Analytics 사용 여부를 선택한 뒤 프로젝트 만들기를 클릭합니다.
    (영문 및 숫자만 허용)

1-2. 안드로이드 앱 등록

  1. 프로젝트 개요 페이지에서 앱추가 > Android 아이콘을 클릭합니다.
  2. 안드로이드 앱의 패키지 이름을 입력합니다. (예: com.example.myapp)
  3. google-services.json 파일을 다운로드합니다.
  4. 다운로드한 google-services.json을 안드로이드 프로젝트의 app/ 폴더에 복사합니다.

1-3. 서비스 계정 키 발급 (서버용)

Python 서버에서 FCM 메시지를 전송하려면 서비스 계정 키가 필요합니다.

  1. Firebase 콘솔 → com.example.myapp프로젝트 설정서비스 계정 탭으로 이동합니다.
  2. 새 비공개 키 생성 버튼을 클릭합니다.
  3. 다운로드된 JSON 파일(예: serviceAccountKey.json)을 안전한 위치에 보관합니다.
    (프로젝트명-랜덤문자열-firebase-adminsdk-fbsvc-랜덤문자열.json 형식)

주의: 서비스 계정 키는 절대 외부에 노출하거나 GitHub에 커밋하지 마세요.

1-4. 안드로이드 프로젝트에 Firebase 의존성 추가

Groovy DSL (build.gradle)

프로젝트 수준 build.gradle

// build.gradle (Project level)
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.4.1'
    }
}

또는 settings.gradle에 플러그인 관리를 사용하는 경우:

// settings.gradle
pluginManagement {
    plugins {
        id 'com.google.gms.google-services' version '4.4.1' apply false
    }
}

앱 수준 build.gradle

// build.gradle (App level)
plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services'
}

dependencies {
    // Firebase BOM으로 버전 일괄 관리
    implementation platform('com.google.firebase:firebase-bom:33.1.0')
    implementation 'com.google.firebase:firebase-messaging'
}

Kotlin DSL (build.gradle.kts)

Android Studio Giraffe(2022.3.1) 이상의 신규 프로젝트는 기본적으로 Kotlin DSL을 사용합니다.

프로젝트 수준 build.gradle.kts

// build.gradle.kts (Project level)
buildscript {
    dependencies {
        classpath("com.google.gms:google-services:4.4.1")
    }
}

또는 settings.gradle.kts에 플러그인 관리를 사용하는 경우:

// settings.gradle.kts
pluginManagement {
    plugins {
        id("com.google.gms.google-services") version "4.4.1" apply false
    }
}

앱 수준 build.gradle.kts

// build.gradle.kts (App level)
plugins {
    id("com.android.application")
    id("com.google.gms.google-services")
}

dependencies {
    // Firebase BOM으로 버전 일괄 관리
    implementation(platform("com.google.firebase:firebase-bom:33.1.0"))
    implementation("com.google.firebase:firebase-messaging")
}

참고: Groovy DSL과의 주요 차이점은 문자열을 '작은따옴표' 대신 "큰따옴표"로 감싸고, 함수 호출 시 괄호 ()를 생략할 수 없다는 점입니다.

Version Catalog + Kotlin DSL (권장)

Android Studio Hedgehog(2023.1.1) 이상 신규 프로젝트의 기본 방식입니다. 버전 정보를 gradle/libs.versions.toml 한 곳에서 중앙 관리하므로, 멀티 모듈 프로젝트에서 특히 유용합니다.

gradle/libs.versions.toml

[versions]
googleServices = "4.4.1"
firebaseBom    = "33.1.0"

[libraries]
firebase-bom       = { group = "com.google.firebase", name = "firebase-bom",       version.ref = "firebaseBom" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" }
# firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging-ktx" }

[plugins]
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }

프로젝트 수준 build.gradle.kts

// build.gradle.kts (Project level)
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.google.services)     apply false
    // 기타 프로젝트에서 사용하는 플러그인 ...
}

앱 수준 build.gradle.kts

// build.gradle.kts (App level)
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.google.services)
}

dependencies {
    // toml에 정의된 라이브러리를 alias로 참조
    implementation(platform(libs.firebase.bom))
    implementation(libs.firebase.messaging)
}

참고: libs.plugins.android.application 처럼 .으로 연결된 alias는 toml의 -.으로 변환한 것입니다. (예: android-applicationlibs.plugins.android.application)

Gradle Sync를 실행하면 설정이 완료됩니다.


2. Python 앱으로 토픽 푸시 발송

Firebase Admin SDK를 사용해 특정 토픽을 구독 중인 모든 기기에 푸시를 전송합니다.

2-1. 패키지 설치

pip install firebase-admin

2-2. 기본 토픽 메시지 전송

# send_push.py
import firebase_admin
from firebase_admin import credentials, messaging

# 서비스 계정 키로 Firebase 초기화 (최초 1회만 실행)
cred = credentials.Certificate("serviceAccountKey.json")
firebase_admin.initialize_app(cred)

def send_topic_message(topic: str, title: str, body: str, data: dict = None):
    """
    특정 토픽을 구독한 모든 기기에 푸시 메시지를 전송합니다.

    :param topic: 구독 토픽 이름 (예: "news", "alerts")
    :param title: 알림 제목
    :param body:  알림 본문
    :param data:  커스텀 데이터 페이로드 (선택 사항)
    """
    message = messaging.Message(
        notification=messaging.Notification(
            title=title,
            body=body,
        ),
        data=data or {},
        topic=topic,
    )

    response = messaging.send(message)
    print(f"[성공] 메시지 ID: {response}")
    return response

if __name__ == "__main__":
    send_topic_message(
        topic="news",
        title="오늘의 뉴스 📰",
        body="새로운 기사가 등록되었습니다. 지금 확인해보세요!",
        data={"article_id": "12345", "category": "tech"},
    )

2-3. 안드로이드 전용 옵션 추가

안드로이드 기기에서 알림 채널, 아이콘, 우선순위 등을 세밀하게 제어할 수 있습니다.

import firebase_admin
from firebase_admin import credentials, messaging

cred = credentials.Certificate("serviceAccountKey.json")
firebase_admin.initialize_app(cred)

def send_topic_message_android(topic: str, title: str, body: str):
    message = messaging.Message(
        notification=messaging.Notification(
            title=title,
            body=body,
        ),
        android=messaging.AndroidConfig(
            priority="high",                    # 메시지 우선순위 (normal / high)
            notification=messaging.AndroidNotification(
                title=title,
                body=body,
                icon="ic_notification",         # res/drawable 아이콘 이름
                color="#FF5722",                # 아이콘 색상 (Hex)
                channel_id="default_channel",   # 안드로이드 알림 채널 ID
                sound="default",
                click_action="OPEN_MAIN_ACTIVITY",  # 알림 클릭 시 실행할 인텐트 액션
            ),
        ),
        data={
            "type": "NEWS",
            "target_id": "42",
        },
        topic=topic,
    )

    response = messaging.send(message)
    print(f"[성공] 메시지 ID: {response}")
    return response

if __name__ == "__main__":
    send_topic_message_android(
        topic="mytopic",
        title="속보 🚨",
        body="중요한 뉴스가 도착했습니다!",
    )

2-4. 실행

python send_push.py

성공 시 아래와 같이 메시지 ID가 출력됩니다.

[성공] 메시지 ID: projects/your-project-id/messages/0:1234567890abcdef

3. 안드로이드 앱에서 토픽 수신

3-1. FirebaseMessagingService 구현

app/src/main/java/.../MyFirebaseMessagingService.kt 파일을 생성합니다.

// MyFirebaseMessagingService.kt
package com.example.myapp

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService : FirebaseMessagingService() {

    companion object {
        private const val CHANNEL_ID = "default_channel"
        private const val CHANNEL_NAME = "기본 알림"
    }

    /**
     * FCM 토큰이 새로 발급되거나 갱신될 때 호출됩니다.
     * 서버에 최신 토큰을 저장하려면 여기서 업로드 로직을 추가하세요.
     */
    override fun onNewToken(token: String) {
        super.onNewToken(token)
        // TODO: 서버로 토큰 전송
        val topic = "mytopic"
        if (topic != null) {
            FirebaseMessaging.getInstance().subscribeToTopic(topic)
        }
    }

    /**
     * 포그라운드 상태에서 FCM 메시지를 수신할 때 호출됩니다.
     * 백그라운드/종료 상태에서는 시스템이 자동으로 알림을 표시합니다.
     */
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)

        val title = remoteMessage.notification?.title ?: "새 알림"
        val body  = remoteMessage.notification?.body  ?: ""
        val data  = remoteMessage.data  // 커스텀 데이터 Map

        // 커스텀 데이터 처리 예시
        val articleId = data["article_id"]
        val type      = data["type"]

        showNotification(title, body)
    }

    private fun showNotification(title: String, body: String) {
        val notificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Android 8.0 (Oreo) 이상은 알림 채널이 필수
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_HIGH
            )
            notificationManager.createNotificationChannel(channel)
        }

        val intent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
        }
        val pendingIntent = PendingIntent.getActivity(
            this, 0, intent,
            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(body)
            .setAutoCancel(true)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setContentIntent(pendingIntent)
            .build()

        notificationManager.notify(System.currentTimeMillis().toInt(), notification)
    }
}

3-2. AndroidManifest.xml 등록

<!-- AndroidManifest.xml -->
<application ... >

    <!-- FCM 서비스 등록 -->
    <service
        android:name=".MyFirebaseMessagingService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>

    <!-- 기본 알림 채널 ID 설정 (백그라운드 수신 시 사용) -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_channel_id"
        android:value="default_channel" />

    <!-- 기본 알림 아이콘 설정 -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_icon"
        android:resource="@drawable/ic_notification" />

</application>

3-3. 토픽 구독 및 구독 해제

앱이 실행될 때 원하는 토픽을 구독합니다.

// MainActivity.kt
import com.google.firebase.messaging.FirebaseMessaging

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        subscribeToTopic("news")
    }

    /** 특정 토픽 구독 */
    private fun subscribeToTopic(topic: String) {
        FirebaseMessaging.getInstance().subscribeToTopic(topic)
            .addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    // 구독 성공
                } else {
                    // 구독 실패
                }
            }
    }

    /** 특정 토픽 구독 해제 */
    private fun unsubscribeFromTopic(topic: String) {
        FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
            .addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    // 구독 해제 성공
                }
            }
    }
}

3-4. 포그라운드 / 백그라운드 동작 차이

상태 동작
포그라운드 onMessageReceived() 호출 → 개발자가 직접 알림을 표시해야 함
백그라운드 시스템이 notification 페이로드를 자동으로 상태 바에 표시
앱 종료 시스템이 notification 페이로드를 자동으로 상태 바에 표시

💡 : 포그라운드에서도 알림을 자동으로 표시하려면 onMessageReceived() 안에서 직접 NotificationManager를 호출해야 합니다.


전체 흐름 요약

[Python 서버]
    │
    │  Firebase Admin SDK로
    │  "news" 토픽에 메시지 전송
    ▼
[Firebase FCM 서버]
    │
    │  "news" 토픽을 구독한
    │  모든 기기에 메시지 전달
    ▼
[안드로이드 앱]
    ├─ 포그라운드: onMessageReceived() → 커스텀 알림 표시
    └─ 백그라운드/종료: 시스템이 알림 자동 표시

참고 자료

답글 남기기