- 무료로 전송할 수 있는 교차 플랫폼 메시징 솔루션
- 모든 사용자에게 알림 메시지를 전송할 수도 있고, 그룹을 지어 메시지를 전송할 수도 있다.
FCM을 이용하면 플랫폼에 종속되지 않고 푸쉬 메시지를 전송할 수 있다.
iOS, Android, Web 각 플랫폼에서 푸쉬 메시지를 보내려면 각 플랫폼 환경별로 개발해야하는 불편함이 있는데 이를 하나로 통합하는 솔루션
서버를 경유해서 실시간으로 푸쉬 메시지를 받으려면 사용자는 항상 서버에 접속해 있어야 하는데 이는 사용자 기기의 배터리 및 네트워크 리소스를 크게 낭비한다.
클라우드 메시징 서버를 중간에 둠으로써, 사용자는 낮은 배터리와 네트워크의 사용만으로도 메시지를 실시간으로 송수신 처리를 할 수 있다.
EX) A -> 어플리케이션 서버 -> 클라우드 메시징 서버 -> B
크게 송신자, FCM Backend Server, 수신자로 구분한다.
송신자는 주로 앱 서버, HTTP 프로토콜을 사용하는 서버, Firebase Console GUI 등이 될 수 있고, 수신자는 우리가 흔히 사용하는 iOS 또는 Android 운영체제를 사용하는 모바일 기기가 될 수 있다.
FCM Backend 서버는 실질적으로 앱 서버에서 요청을 받아서 메세지를 처리하는 서버에 해당된다.
FCM 클라우드 메시지의 흐름
앱 서버에서 FCM Backend 서버에 클라이언트 앱에 보내고자 하는 메시지를 담은 정보와, 서버의 인증 정보 클라이언트의 토큰을 담아서 HTTP POST를 요청 보낸다.
요청을 받은 FCM Backend 서버는 요처을 통해 받은 메시지의 이상 유무에 따라 앱 서버에 적절한 응답을 보낸다.
이후 FCM Backend 서버에서 여러가지(우선 순위, 클라이언트 앱과의 통신 가능 여부 등)을 고려하여 메세지를 클라이언트 앱에 보낸다.
클라이언트 앱에서는 받은 메세지를 적절하게 처리하고 응답 메세지를 FCM Backend 서버로 보내게 된다.
요약하면, 앱 서버에서 FCM Backend 서버에 메시지 요청을 보내고, FCM Backend 서버는 사용자 기기에서 실행되는 클라이언트 앱에 메시지를 보내게 된다.
제대로 된 메시지만 만들어 주면 그것을 실제 기기로 전달하는 것은 FCM Backend에서 처리해줄 것이란 얘기다.
단, 위의 플로우 대로 메시지를 전송하려면 반드시 선행되어야 하는 작업이 있는데, 메시지를 수신할 클라이언트는 자신의 정보를 FCM Backend 서버에 등록해야 한다.
FCM을 사용하기 위해 FCM Backend 서버와 통신을 위한 어플리케이션 서버를 구축하기 위해선 다음 3가지 규칙을 지켜야 한다.
FCM Backend 서버에 FCM에서 지정한 형식의 메시지 요청을 보낼 수 있어야 한다.
지수 백오프를 사용하여 요청을 처리하고 다시 보낼 수 있어야 한다.
서버 승인 사용자의 인증 정보
와 클라이언트의 등록 토큰
을 안전하게 저장할 수 있어야 한다.
내가 작성한 샘플 코드의 경우, FCM을 서버단에서 사용할 경우의 샘플 코드이고, 언어 및 프레임워크는 Java와 Spring이다.
초기 Dependency 설정. (Firebase Admin SDK 추가)
Firebase 의존성 추가
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>6.8.1</version>
</dependency>
SDK 초기화
SDK 초기화를 진행할 때 Firebase 인증 처리를 해줘야 하는데, 먼저 Firebase 프로젝트 생성 후, 거기 설정에서 키를 발급받아야 한다.
여기선, GCP의 서비스 계정 그룹 안에 Firebase가 이미 등록되어 있어서, GCP Credential로 인증 처리를 해주었다.
private FirebaseApp createFirebaseApp() {
FirebaseOptions options = null;
ClassPathResource resource =
new ClassPathResource(gcpCredentialFile);
try (InputStream is = resource.getInputStream()) {
options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(is))
.setDatabaseUrl("https://[My-Database-URL]
.firebaseio.com")
.build();
} catch (IOException e) {
log.error(e.getMessage());
}
return FirebaseApp.initializeApp(options);
}
특정 기기에 대한 메시지 전송
private void sendToToken(Push push) throws FirebaseMessagingException {
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("[광고] 테스트 광고")
.setBody("테스트 내용")
.build())
// Device를 특정할 수 있는 토큰.
.setToken(push.getRegistrationToken())
.build();
String response = FirebaseMessaging.getInstance().send(message);
log.debug("Successfully sent message: " + response);
}
여러 기기에 대한 메세지 전송
private void multipleSendToToken(Push push)
throws FirebaseMessagingException {
List<String> tokenList = IntStream.rangeClosed(1, 30).mapToObj(index
-> push.getRegistrationToken()).collect(Collectors.toList());
MulticastMessage message = MulticastMessage.builder()
.setNotification(Notification.builder()
.setTitle("[많은 양의 메세지] 알람입니다.")
.setBody("많은 알람에 당황하지 마세요.")
.build())
.addAllTokens(tokenList)
.build();
BatchResponse response = FirebaseMessaging.getInstance().sendMulticast(message);
if (response.getFailureCount() > 0) {
List<SendResponse> responses = response.getResponses();
List<String> failedTokens = new ArrayList<>();
for (int i = 0; i < responses.size(); i++) {
if (!responses.get(i).isSuccessful()) {
failedTokens.add(tokenList.get(i));
}
}
System.out.println("List of tokens that caused failures: " + failedTokens);
}
}
특정 주제 구독 및 주제 구독자에게 메세지 전송
구독하고자 하는 주제가 존재하지 않는다면 FCM Backend에서 새롭게 생성해주고, 존재한다면 해당 주제에 구독처리가 된다.
한 기기가 여러 주제에 구독했다면 구독한 주제별로 보내는 메세지를 모두 받게 된다.
private void sendSubscribeTopic(Push push)
throws FirebaseMessagingException {
List<String> registrationTokens =
Collections.singletonList(push.getRegistrationToken());
TopicManagementResponse response = FirebaseMessaging.getInstance()
.subscribeToTopic(Collections.singletonList
(push.getRegistrationToken()), "Apple News");
System.out.println(response.getSuccessCount() +
" tokens were subscribed successfully");
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("제목")
.setBody("내용")
.build())
.setTopic("Apple News")
.build();
String response2 = FirebaseMessaging.getInstance().send(message);
log.debug("Successfully sent message: " + response2);
}
일괄 메시지 전송
하나의 메시지를 많은 사람들에게 보낼 수 있을 뿐만 아니라, 다양한 메시지를 사람들에게 일괄 전송하는 것도 가능
private void multipleEachOtherSend(Push push)
throws FirebaseMessagingException {
List<Message> messages = Arrays.asList(
Message.builder()
.setNotification(Notification.builder()
.setTitle("첫 번째 제목")
.setBody("첫 번째 내용")
.build())
.setToken(push.getRegistrationToken())
.build(),
Message.builder()
.setNotification(Notification.builder()
.setTitle("두 번째 제목")
.setBody("두 번째 내용")
.build())
.setTopic("토픽")
.build()
);
BatchResponse response = FirebaseMessaging.getInstance()
.sendAll(messages);
System.out.println(response.getSuccessCount() +
" messages were sent successfully");
}
감사합니다. 이런 정보를 나눠주셔서 좋아요.