FCM#
FCM(Firebase Cloud Messaging)은 Google에서 제공하는 크로스 플랫폼 메시징 솔루션으로, 안정적이고 효율적으로 메시지를 전송할 수 있는 서비스이다. 모바일 앱 및 웹 애플리케이션에 **푸시 알림(push notification)**을 쉽고 안정적으로 전송할 수 있다.
FCM의 기본 개념#
FCM은 Google Cloud Messaging(GCM)의 후속 서비스로, 다양한 플랫폼(Android, iOS, 웹)에서 푸시 알림을 보낼 수 있는 통합 솔루션이다.
FCM의 주요 특징은 다음과 같다:
- 멀티 플랫폼 지원
Android, iOS, 그리고 웹까지 지원하여 크로스플랫폼으로 동작한다. - 무료 사용
별도의 비용 없이 대량의 메시지를 무제한으로 전송할 수 있다. - 토픽/그룹 메시징
특정 사용자 집합(예: 관심사를 가진 그룹, 특정 주제 토픽)에 메시지 발송이 가능하다. - 고급 메시지 옵션
데이터 메시지(앱이 직접 메시지 데이터를 처리)와 알림 메시지(FCM이 자동으로 기기 알림창에 표시) 타입을 구분해서 활용할 수 있다. - 강력한 통계 및 관리
전송 현황 및 전송 실패, 수신 확인 등 다양한 통계와 관리 기능을 제공한다. - 조건부 전송
여러 속성(국가, 디바이스 유형 등)에 따라 복잡한 조건 기반 전송이 가능하다. 독자 등 대상 지정 가능
FCM의 동작 원리#
서버와 클라이언트 구조#
- 클라이언트 앱: 사용자의 스마트폰 또는 웹 브라우저. FCM으로부터 메시지를 수신한다.
- FCM 백엔드: Google의 클라우드 서버에서 메시지 중계/전달.
- 앱 서버(옵션): 개발자가 직접 운영하는 서버로, FCM 서버에 메시지 전송을 요청.
메시지 전송 흐름#
- 사용자의 디바이스에서 FCM SDK를 통해 FCM 서버에 **등록 토큰(Registration Token)**을 요청 및 발급.
- 앱 서버 또는 Firebase Console을 통해 메시지를 전송 요청.
- FCM 서버가 등록 토큰(또는 토픽/그룹)에 해당하는 디바이스로 메시지를 중계.
- 최종적으로 디바이스에 메시지가 도착, 알림이 표시되거나 앱에서 직접 데이터 처리.
FCM 메시지 유형#
FCM에서는 두 가지 주요 메시지 유형을 제공한다:
알림 메시지(Notification Messages)#
사용자에게 직접 표시되는 메시지이다.
FCM SDK가 자동으로 UI 알림을 생성한다.
1
2
3
4
5
6
7
8
9
| {
"message": {
"token": "사용자_토큰",
"notification": {
"title": "새로운 메시지",
"body": "회원님을 위한 특별 할인 정보입니다."
}
}
}
|
데이터 메시지(Data Messages)#
앱이 직접 처리해야 하는 커스텀 키-값 쌍을 포함한다. 백그라운드 처리나 사용자 정의 알림에 유용하다.
1
2
3
4
5
6
7
8
9
10
| {
"message": {
"token": "사용자_토큰",
"data": {
"type": "promotion",
"discount": "30%",
"expiry": "2025-04-20"
}
}
}
|
FCM 구현 방법#
Firebase 프로젝트 설정#
- Firebase 콘솔(https://console.firebase.google.com/)에 접속
- 프로젝트 생성
- 각 플랫폼(Android, iOS, 웹)별로 앱 추가
안드로이드 구현#
build.gradle 설정
1
2
3
4
| dependencies {
// FCM 라이브러리 추가
implementation 'com.google.firebase:firebase-messaging:23.2.1'
}
|
매니페스트 설정
1
2
3
4
5
6
7
8
9
10
11
12
| <manifest>
<application>
<!-- FCM 서비스 등록 -->
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
|
메시징 서비스 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| public class MyFirebaseMessagingService extends FirebaseMessagingService {
// 새 토큰 발급 시 호출
@Override
public void onNewToken(String token) {
// 토큰을 서버에 전송하는 로직
sendRegistrationToServer(token);
}
// 메시지 수신 시 호출
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// 알림 메시지 처리
if (remoteMessage.getNotification() != null) {
showNotification(
remoteMessage.getNotification().getTitle(),
remoteMessage.getNotification().getBody()
);
}
// 데이터 메시지 처리
if (remoteMessage.getData().size() > 0) {
handleDataMessage(remoteMessage.getData());
}
}
// 기타 필요한 메서드 구현
}
|
iOS 구현#
Podfile 설정
1
| pod 'Firebase/Messaging'
|
앱 델리게이트 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| import Firebase
import FirebaseMessaging
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Firebase 초기화
FirebaseApp.configure()
// FCM 설정
Messaging.messaging().delegate = self
// 알림 권한 요청
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
application.registerForRemoteNotifications()
return true
}
// 토큰 갱신 시 호출
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
if let token = fcmToken {
// 토큰을 서버에 전송하는 로직
sendRegistrationToServer(token)
}
}
}
// 알림 처리를 위한 확장
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// 앱 포그라운드 상태에서 알림 처리
completionHandler([.alert, .badge, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// 알림 탭 처리
completionHandler()
}
}
|
웹 구현#
Firebase SDK 추가
1
2
| <script src="https://www.gstatic.com/firebasejs/9.21.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.21.0/firebase-messaging.js"></script>
|
Firebase 초기화 및 메시징 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // Firebase 설정
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
// Firebase 초기화
firebase.initializeApp(firebaseConfig);
// FCM 인스턴스 가져오기
const messaging = firebase.messaging();
// 서비스 워커 등록
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then((registration) => {
messaging.useServiceWorker(registration);
// 알림 권한 요청
messaging.requestPermission()
.then(() => {
console.log('알림 권한 획득');
return messaging.getToken();
})
.then((token) => {
console.log('FCM 토큰:', token);
// 토큰을 서버에 전송하는 로직
sendTokenToServer(token);
})
.catch((err) => {
console.log('알림 권한 획득 실패:', err);
});
});
// 포그라운드 메시지 수신
messaging.onMessage((payload) => {
console.log('메시지 수신:', payload);
// 수신된 메시지 처리 로직
showNotification(payload);
});
|
서비스 워커 파일(firebase-messaging-sw.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| importScripts('https://www.gstatic.com/firebasejs/9.21.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/9.21.0/firebase-messaging.js');
// Firebase 설정
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
// Firebase 초기화
firebase.initializeApp(firebaseConfig);
// FCM 인스턴스
const messaging = firebase.messaging();
// 백그라운드 메시지 처리
messaging.onBackgroundMessage((payload) => {
console.log('백그라운드 메시지 수신:', payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: '/firebase-logo.png'
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
|
FCM 서버 측 구현#
서버 측 라이브러리#
다양한 프로그래밍 언어로 FCM을 사용할 수 있다:
- Node.js: firebase-admin SDK
- Java: Firebase Admin Java SDK
- Python: firebase-admin
- Go: firebase-admin-go
Node.js 예제#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
| // Firebase Admin SDK 초기화
const admin = require('firebase-admin');
const serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
// 메시지 전송 함수
function sendFCMMessage(token, title, body, data) {
// 메시지 구성
const message = {
notification: {
title: title,
body: body
},
data: data || {},
token: token
};
// 메시지 전송
admin.messaging().send(message)
.then((response) => {
console.log('메시지 전송 성공:', response);
})
.catch((error) => {
console.log('메시지 전송 실패:', error);
});
}
// 주제 메시지 전송 함수
function sendTopicMessage(topic, title, body, data) {
// 메시지 구성
const message = {
notification: {
title: title,
body: body
},
data: data || {},
topic: topic
};
// 메시지 전송
admin.messaging().send(message)
.then((response) => {
console.log('주제 메시지 전송 성공:', response);
})
.catch((error) => {
console.log('주제 메시지 전송 실패:', error);
});
}
// 멀티캐스트 메시지 전송 함수
function sendMulticastMessage(tokens, title, body, data) {
// 메시지 구성
const message = {
notification: {
title: title,
body: body
},
data: data || {},
tokens: tokens // 최대 500개 토큰
};
// 메시지 전송
admin.messaging().sendMulticast(message)
.then((response) => {
console.log('성공한 메시지 수:', response.successCount);
console.log('실패한 메시지 수:', response.failureCount);
})
.catch((error) => {
console.log('멀티캐스트 메시지 전송 실패:', error);
});
}
|
FCM 고급 기능#
주제 구독(Topic Subscription)#
사용자를 특정 주제에 구독시켜 해당 주제로 메시지를 보낼 수 있다.
클라이언트 측 구독(Android)
1
2
3
4
5
6
7
8
| FirebaseMessaging.getInstance().subscribeToTopic("news")
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Log.d(TAG, "뉴스 주제 구독 성공");
} else {
Log.e(TAG, "주제 구독 실패", task.getException());
}
});
|
서버 측 메시지 전송
1
2
3
4
5
6
7
8
9
10
11
12
| admin.messaging().sendToTopic('news', {
notification: {
title: '최신 뉴스',
body: '오늘의 헤드라인 뉴스입니다.'
}
})
.then((response) => {
console.log('주제 메시지 전송 성공:', response);
})
.catch((error) => {
console.log('주제 메시지 전송 실패:', error);
});
|
메시지 그룹화#
연관된 알림을 그룹화하여 사용자 경험을 개선할 수 있다.
Android 예제
1
2
3
4
5
6
| NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(R.drawable.ic_notification)
.setGroup("message_group") // 그룹 키 설정
.setGroupSummary(isGroupSummary); // 그룹 요약 알림 여부
|
조건부 메시지 전송#
복잡한 조건식을 사용하여 메시지 대상을 지정할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| const condition = "'premium' in topics && ('android' in topics || 'ios' in topics)";
admin.messaging().sendToCondition(condition, {
notification: {
title: '프리미엄 사용자 알림',
body: '새로운 프리미엄 기능이 출시되었습니다.'
}
})
.then((response) => {
console.log('조건부 메시지 전송 성공:', response);
})
.catch((error) => {
console.log('조건부 메시지 전송 실패:', error);
});
|
FCM 활용 사례#
마케팅 캠페인#
사용자 세그먼트별로 타겟팅된 프로모션 메시지를 전송할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
| // 최근 1주일간 앱을 사용하지 않은 사용자에게 메시지 전송
admin.messaging().sendToTopic('inactive_users', {
notification: {
title: '돌아오세요!',
body: '놓치고 있는 새로운 기능들이 있습니다.'
},
data: {
offer_id: 'comeback_discount',
discount: '15%'
}
});
|
실시간 업데이트#
뉴스 앱, 스포츠 스코어, 주식 가격 등의 실시간 업데이트를 제공할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
| // 스포츠 경기 스코어 업데이트
admin.messaging().sendToTopic('football_match_123', {
notification: {
title: '골! 대한민국 2-1 일본',
body: '손흥민 선수의 결승골로 2-1 역전승!'
},
data: {
match_id: '123',
time: '87분',
scorer: '손흥민'
}
});
|
사용자 참여 유도#
사용자 참여를 유도하는 맞춤형 메시지를 보낼 수 있다.
1
2
3
4
5
6
7
8
9
10
| // 앱 사용이 저조한 사용자 재참여 유도
admin.messaging().sendToTopic('low_engagement', {
notification: {
title: '새로운 활동이 당신을 기다리고 있어요',
body: '친구들이 당신의 소식을 기다리고 있습니다.'
},
data: {
action: 'open_friends_list'
}
});
|
FCM 문제 해결과 모범 사례#
- 토큰 만료: 클라이언트 토큰은 주기적으로 갱신될 수 있으므로, 서버에서 최신 토큰을 유지해야 한다.
- 메시지 전송 실패: 잘못된 토큰, 초과된 메시지 크기, 네트워크 문제 등으로 발생할 수 있다.
- iOS 알림 표시 문제: APNS 인증서 설정과 백그라운드 모드 설정을 확인해야 한다.
- 배치 처리: 여러 메시지를 단일 요청으로 묶어 전송
- 주제 활용: 유사한 대상에게는 개별 메시지보다 주제 메시지 사용
- 토큰 관리: 주기적으로 토큰 유효성 검사 및 정리
- API 키 보호: FCM 서버 키는 안전하게 보관하고 클라이언트에 노출시키지 않기
- 메시지 인증: 신뢰할 수 있는 서버에서만 메시지 전송
- 민감한 정보 제한: 푸시 알림에 민감한 개인 정보 포함하지 않기
FCM의 미래와 최신 트렌드#
- 최근 업데이트 및 개선사항
- FCM HTTP v1 API: 레거시 HTTP API를 대체하는 새로운 API
- Web Push 표준 지원 강화: 웹 푸시 표준과의 호환성 개선
- 분석 기능 강화: 메시지 전송 및 사용자 참여 분석 도구 개선
- 향후 전망
- AI 기반 메시징: 사용자 행동 분석을 통한 지능형 푸시 알림
- 개인정보 보호 강화: 사용자 데이터 보호를 위한 기능 확대
- 크로스 플랫폼 통합 강화: 다양한 플랫폼에서의 일관된 경험 제공
용어 정리#
참고 및 출처#