FCM

FCM(Firebase Cloud Messaging)은 Google에서 제공하는 크로스 플랫폼 메시징 솔루션으로, 안정적이고 효율적으로 메시지를 전송할 수 있는 서비스이다. 모바일 앱 및 웹 애플리케이션에 **푸시 알림(push notification)**을 쉽고 안정적으로 전송할 수 있다.

FCM의 기본 개념

FCM은 Google Cloud Messaging(GCM)의 후속 서비스로, 다양한 플랫폼(Android, iOS, 웹)에서 푸시 알림을 보낼 수 있는 통합 솔루션이다.

FCM의 주요 특징은 다음과 같다:

FCM의 동작 원리

서버와 클라이언트 구조

메시지 전송 흐름

  1. 사용자의 디바이스에서 FCM SDK를 통해 FCM 서버에 **등록 토큰(Registration Token)**을 요청 및 발급.
  2. 앱 서버 또는 Firebase Console을 통해 메시지를 전송 요청.
  3. FCM 서버가 등록 토큰(또는 토픽/그룹)에 해당하는 디바이스로 메시지를 중계.
  4. 최종적으로 디바이스에 메시지가 도착, 알림이 표시되거나 앱에서 직접 데이터 처리.

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 프로젝트 설정

  1. Firebase 콘솔(https://console.firebase.google.com/)에 접속
  2. 프로젝트 생성
  3. 각 플랫폼(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 예제

 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 문제 해결과 모범 사례

  1. 토큰 만료: 클라이언트 토큰은 주기적으로 갱신될 수 있으므로, 서버에서 최신 토큰을 유지해야 한다.
  2. 메시지 전송 실패: 잘못된 토큰, 초과된 메시지 크기, 네트워크 문제 등으로 발생할 수 있다.
  3. iOS 알림 표시 문제: APNS 인증서 설정과 백그라운드 모드 설정을 확인해야 한다.
  1. 배치 처리: 여러 메시지를 단일 요청으로 묶어 전송
  2. 주제 활용: 유사한 대상에게는 개별 메시지보다 주제 메시지 사용
  3. 토큰 관리: 주기적으로 토큰 유효성 검사 및 정리
  1. API 키 보호: FCM 서버 키는 안전하게 보관하고 클라이언트에 노출시키지 않기
  2. 메시지 인증: 신뢰할 수 있는 서버에서만 메시지 전송
  3. 민감한 정보 제한: 푸시 알림에 민감한 개인 정보 포함하지 않기

FCM의 미래와 최신 트렌드

  1. 최근 업데이트 및 개선사항
  2. FCM HTTP v1 API: 레거시 HTTP API를 대체하는 새로운 API
  3. Web Push 표준 지원 강화: 웹 푸시 표준과의 호환성 개선
  4. 분석 기능 강화: 메시지 전송 및 사용자 참여 분석 도구 개선
  5. 향후 전망
  6. AI 기반 메시징: 사용자 행동 분석을 통한 지능형 푸시 알림
  7. 개인정보 보호 강화: 사용자 데이터 보호를 위한 기능 확대
  8. 크로스 플랫폼 통합 강화: 다양한 플랫폼에서의 일관된 경험 제공

용어 정리

용어설명

참고 및 출처