여러 기기에 메시지를 타겟팅하려면 주제 메시징을 사용합니다. 이 기능을 사용하면 특정 주제를 구독하는 여러 기기에 메시지를 보낼 수 있습니다.
이 튜토리얼에서는 FCM용 Admin SDK 또는 FCM용 REST API를 사용하여 앱 서버에서 주제 메시지를 보내는 방법 및 Android 앱에서 이 메시지를 수신하고 처리하는 방법을 다룹니다. 또한 백그라운드 앱과 포그라운드 앱의 메시지 처리 방법을 설명합니다. 설정에서 검증까지 이 작업에 필요한 모든 단계를 다룹니다.
SDK 설정
FCM용 Android 클라이언트 앱을 설정했거나 첫 번째 메시지 보내기 단계를 마쳤으면 이 섹션에서 설명하는 단계를 이미 완료했을 수 있습니다.
시작하기 전에
Android 스튜디오를 설치하거나 최신 버전으로 업데이트합니다.
프로젝트가 다음 요구사항을 충족하는지 확인합니다(일부 제품의 경우 더 엄격한 요구사항이 적용될 수 있음).
- API 수준 21(Lollipop) 이상 타겟팅
- Android 5.0 이상 사용
- 다음 버전 요구사항을 충족하는 Jetpack(AndroidX) 사용
com.android.tools.build:gradle
v7.3.0 이상compileSdkVersion
28 이상
실제 기기를 설정하거나 에뮬레이터를 사용하여 앱을 실행합니다.
Google Play 서비스에 대한 종속성이 있는 Firebase SDK를 사용하려면 기기 또는 에뮬레이터에 Google Play 서비스가 설치되어 있어야 합니다.Google 계정을 사용하여 Firebase에 로그인합니다.
Android 프로젝트가 준비되지 않았지만 Firebase 제품을 사용해 보고자 하는 경우 빠른 시작 샘플 중 하나를 다운로드하세요.
Firebase 프로젝트 만들기
Firebase를 Android 앱에 추가하려면 먼저 Android 앱에 연결할 Firebase 프로젝트를 만드세요. Firebase 프로젝트에 대한 자세한 내용은 Firebase 프로젝트 이해를 참조하세요.
Firebase에 앱 등록
Android 앱에서 Firebase를 사용하려면 Firebase 프로젝트에 앱을 등록해야 합니다. 앱 등록이란 보통 프로젝트에 앱을 '추가'하는 것을 의미합니다.
Firebase 콘솔로 이동
프로젝트 개요 페이지 중앙에 있는 Android 아이콘(
) 또는 앱 추가를 클릭하여 설정 워크플로를 시작합니다.Android 패키지 이름 필드에 앱의 패키지 이름을 입력합니다.
(선택사항) 다른 앱 정보(앱 닉네임 및 디버그 서명 인증서 SHA-1)를 입력합니다.
앱 등록을 클릭합니다.
Firebase 구성 파일 추가
Firebase Android 구성 파일(
)을 다운로드한 후 앱에 추가합니다.google-services.json google-services.json 다운로드를 클릭하여 Firebase Android 구성 파일을 가져옵니다.
구성 파일을 앱의 모듈(앱 수준) 루트 디렉터리로 이동합니다.
Firebase SDK가
구성 파일의 값에 액세스할 수 있으려면 Google 서비스 Gradle 플러그인(google-services.json google-services
)이 필요합니다.루트 수준(프로젝트 수준) Gradle 파일(
<project>/build.gradle.kts
또는<project>/build.gradle
)에서 Google 서비스 플러그인을 종속 항목으로 추가합니다.Kotlin
plugins { id("com.android.application") version "7.3.0" apply false // ... // Add the dependency for the Google services Gradle plugin id("com.google.gms.google-services") version "4.4.2" apply false }
Groovy
plugins { id 'com.android.application' version '7.3.0' apply false // ... // Add the dependency for the Google services Gradle plugin id 'com.google.gms.google-services' version '4.4.2' apply false }
모듈(앱 수준) Gradle 파일(일반적으로
<project>/<app-module>/build.gradle.kts
또는<project>/<app-module>/build.gradle
)에서 Google 서비스 플러그인을 추가합니다.Kotlin
plugins { id("com.android.application") // Add the Google services Gradle plugin id("com.google.gms.google-services") // ... }
Groovy
plugins { id 'com.android.application' // Add the Google services Gradle plugin id 'com.google.gms.google-services' // ... }
앱에 Firebase SDK 추가
모듈(앱 수준) Gradle 파일(일반적으로
<project>/<app-module>/build.gradle.kts
또는<project>/<app-module>/build.gradle
)에서 Android용 Firebase Cloud Messaging 라이브러리의 종속 항목을 추가합니다. 라이브러리 버전 관리 제어에는 Firebase Android BoM을 사용하는 것이 좋습니다.Firebase Cloud Messaging 사용 환경을 최적화하려면 Firebase 프로젝트에서 Google Analytics를 사용 설정하고 Google 애널리틱스용 Firebase SDK를 앱에 추가하는 것이 좋습니다.
dependencies { // Import the BoM for the Firebase platform implementation(platform("com.google.firebase:firebase-bom:33.6.0")) // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries // When using the BoM, you don't specify versions in Firebase library dependencies implementation("com.google.firebase:firebase-messaging") implementation("com.google.firebase:firebase-analytics") }
Firebase Android BoM을 사용하면 앱에서 항상 호환되는 Firebase Android 라이브러리 버전만 사용합니다.
(대안) BoM을 사용하지 않고 Firebase 라이브러리 종속 항목을 추가합니다.
Firebase BoM을 사용하지 않도록 선택한 경우에는 종속 항목 줄에 각 Firebase 라이브러리 버전을 지정해야 합니다.
앱에서 여러 Firebase 라이브러리를 사용하는 경우 모든 버전이 호환되도록 BoM을 사용하여 라이브러리 버전을 관리하는 것이 좋습니다.
dependencies { // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries // When NOT using the BoM, you must specify versions in Firebase library dependencies implementation("com.google.firebase:firebase-messaging:24.1.0") implementation("com.google.firebase:firebase-analytics:22.1.2") }
Android 프로젝트를 Gradle 파일과 동기화합니다.
클라이언트 앱에서 주제 구독
클라이언트 앱에서 기존 주제를 구독하거나 새 주제를 만들 수 있습니다. 클라이언트 앱에서 Firebase 프로젝트에 아직 없는 새 주제 이름을 구독하면 FCM에서 이 이름으로 새 주제가 만들어지고, 이후에 다른 클라이언트에서 그 주제를 구독할 수 있습니다.
주제를 구독하려면 클라이언트 앱에서 FCM 주제 이름과 함께 Firebase Cloud Messaging subscribeToTopic()
을 호출합니다. 이 메서드는 완료 리스너가 구독 성공 여부를 확인하는 데 사용할 수 있는 Task
를 반환합니다.
Kotlin+KTX
Firebase.messaging.subscribeToTopic("weather") .addOnCompleteListener { task -> var msg = "Subscribed" if (!task.isSuccessful) { msg = "Subscribe failed" } Log.d(TAG, msg) Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() }
Java
FirebaseMessaging.getInstance().subscribeToTopic("weather") .addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { String msg = "Subscribed"; if (!task.isSuccessful()) { msg = "Subscribe failed"; } Log.d(TAG, msg); Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show(); } });
구독을 취소하려면 클라이언트 앱에서 주제 이름과 함께 Firebase Cloud Messaging unsubscribeFromTopic()
을 호출합니다.
주제 메시지 수신 및 처리
FCM은 다른 다운스트림 메시지와 동일한 방식으로 주제 메시지를 전송합니다.
메시지를 수신하려면 FirebaseMessagingService
를 확장하는 서비스를 사용합니다.
서비스에서 onMessageReceived
및 onDeletedMessages
콜백을 재정의해야 합니다.
메시지를 처리하는 데 걸리는 시간은 onMessageReceived
호출 전에 발생하는 지연에 따라 20초보다 짧아질 수도 있습니다. 여기에는 OS 지연, 앱 시작 시간, 다른 작업에 의해 차단되는 기본 스레드 또는 긴 시간이 소요되는 이전 onMessageReceived
호출이 포함됩니다. 이 시간이 지나면 Android의 프로세스 종료 또는 Android O의 백그라운드 실행 제한과 같은 여러 OS 동작으로 인해 작업을 완료하는 데 지장이 있을 수 있습니다.
onMessageReceived
는 다음 경우를 제외하고 대부분의 메시지 유형에 제공됩니다.
-
앱이 백그라운드에서 실행되고 있을 때 전송된 알림 메시지: 이 경우 알림이 기기의 작업 표시줄로 전송됩니다. 사용자가 알림을 탭하면 기본적으로 앱 런처가 열립니다.
-
알림과 데이터 페이로드가 둘 다 포함된 메시지(백그라운드에서 수신된 경우): 이 경우 알림은 기기의 작업 표시줄로 전송되고 데이터 페이로드는 런처 활동의 인텐트 부가 정보로 전송됩니다.
요약하면 다음과 같습니다.
앱 상태 | 알림 | 데이터 | 둘 다 |
---|---|---|---|
포그라운드 | onMessageReceived |
onMessageReceived |
onMessageReceived |
배경 | 작업 표시줄 | onMessageReceived |
알림: 작업 표시줄 데이터: 인텐트 부가 정보 |
앱 매니페스트 수정
FirebaseMessagingService
를 사용하려면 앱 매니페스트에 다음을 추가해야 합니다.
<service android:name=".java.MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
또한 알림의 모양을 맞춤설정하는 기본값을 설정하는 것이 좋습니다. 커스텀 기본 아이콘 및 색상을 지정하여 알림 페이로드에 해당 값이 설정되지 않았을 때 항상 지정된 값이 적용되게 할 수 있습니다.
application
태그 안에 다음 줄을 추가하여 커스텀 기본 아이콘 및 색상을 설정합니다.
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages. See README(https://goo.gl/l4GJaQ) for more. --> <meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/ic_stat_ic_notification" /> <!-- Set color used with incoming notification messages. This is used when no color is set for the incoming notification message. See README(https://goo.gl/6BKBk7) for more. --> <meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" />
Android는 다음 경우에 커스텀 기본 아이콘을 표시합니다.
- 알림 작성기에서 보낸 모든 알림 메시지
- 알림 페이로드에 아이콘이 명시적으로 설정되지 않은 모든 알림 메시지
Android는 다음 경우에 커스텀 기본 색상을 사용합니다.
- 알림 작성기에서 보낸 모든 알림 메시지
- 알림 페이로드에 색상이 명시적으로 설정되지 않은 모든 알림 메시지
커스텀 기본 아이콘이 설정되지 않았고 알림 페이로드에도 아이콘이 설정되지 않았으면 Android에서 애플리케이션 아이콘을 흰색으로 렌더링해 표시합니다.
onMessageReceived
재정의
FirebaseMessagingService.onMessageReceived
메서드를 재정의하면 수신된 RemoteMessage 객체를 기준으로 작업을 수행하고 메시지 데이터를 가져올 수 있습니다.
Kotlin+KTX
override fun onMessageReceived(remoteMessage: RemoteMessage) { // TODO(developer): Handle FCM messages here. // Not getting messages here? See why this may be: https://goo.gl/39bRNJ Log.d(TAG, "From: ${remoteMessage.from}") // Check if message contains a data payload. if (remoteMessage.data.isNotEmpty()) { Log.d(TAG, "Message data payload: ${remoteMessage.data}") // Check if data needs to be processed by long running job if (needsToBeScheduled()) { // For long-running tasks (10 seconds or more) use WorkManager. scheduleJob() } else { // Handle message within 10 seconds handleNow() } } // Check if message contains a notification payload. remoteMessage.notification?.let { Log.d(TAG, "Message Notification Body: ${it.body}") } // Also if you intend on generating your own notifications as a result of a received FCM // message, here is where that should be initiated. See sendNotification method below. }
Java
@Override public void onMessageReceived(RemoteMessage remoteMessage) { // TODO(developer): Handle FCM messages here. // Not getting messages here? See why this may be: https://goo.gl/39bRNJ Log.d(TAG, "From: " + remoteMessage.getFrom()); // Check if message contains a data payload. if (remoteMessage.getData().size() > 0) { Log.d(TAG, "Message data payload: " + remoteMessage.getData()); if (/* Check if data needs to be processed by long running job */ true) { // For long-running tasks (10 seconds or more) use WorkManager. scheduleJob(); } else { // Handle message within 10 seconds handleNow(); } } // Check if message contains a notification payload. if (remoteMessage.getNotification() != null) { Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); } // Also if you intend on generating your own notifications as a result of a received FCM // message, here is where that should be initiated. See sendNotification method below. }
onDeletedMessages
재정의
경우에 따라 FCM에서 메시지를 전송하지 못할 수 있습니다. 특정 기기가 연결될 때 앱에서 대기 중인 메시지가 너무 많거나(100개 초과) 기기가 한 달 넘게 FCM에 연결되지 않으면 이 문제가 발생합니다. 이러한 경우 FirebaseMessagingService.onDeletedMessages()
에 대한 콜백이 수신될 수 있습니다. 이 콜백을 수신한 앱 인스턴스는 앱 서버와 전체 동기화를 수행해야 합니다. 해당 기기의 앱으로 메시지를 보낸 지 4주 이상 경과한 경우 FCM은 onDeletedMessages()
를 호출하지 않습니다.
백그라운드 앱에서 알림 메시지 처리
앱이 백그라운드 상태이면 Android에서 알림 메시지를 작업 표시줄로 전송합니다. 사용자가 알림을 탭하면 기본적으로 앱 런처가 열립니다.
여기에는 알림과 데이터 페이로드가 둘 다 포함된 메시지 및 알림 콘솔에서 보낸 모든 메시지가 포함됩니다. 이러한 경우 알림은 기기의 작업 표시줄로 전송되고 데이터 페이로드는 런처 활동의 인텐트 부가 정보로 전송됩니다.
앱으로 전송된 메시지의 통계를 파악하려면, Apple 및 Android 기기에서 열린 전송 메시지 수와 Android 앱의 '노출수'(사용자에게 표시된 알림) 데이터가 기록된 FCM 보고 대시보드를 확인합니다.
보내기 요청 작성
주제를 만든 후에는 클라이언트 측의 클라이언트 앱 인스턴스에서 주제를 구독하거나 서버 API를 통해 주제에 메시지를 전송할 수 있습니다. FCM용 전송 요청을 처음 작성하는 경우 서버 환경 및 FCM 가이드에서 중요한 배경 및 설정 정보를 참조하세요.
백엔드의 전송 로직에서 아래와 같이 원하는 주제 이름을 지정합니다.
Node.js
// The topic name can be optionally prefixed with "/topics/".
const topic = 'highScores';
const message = {
data: {
score: '850',
time: '2:45'
},
topic: topic
};
// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
자바
// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";
// See documentation on defining a message payload.
Message message = Message.builder()
.putData("score", "850")
.putData("time", "2:45")
.setTopic(topic)
.build();
// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# The topic name can be optionally prefixed with "/topics/".
topic = 'highScores'
# See documentation on defining a message payload.
message = messaging.Message(
data={
'score': '850',
'time': '2:45',
},
topic=topic,
)
# Send a message to the devices subscribed to the provided topic.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// The topic name can be optionally prefixed with "/topics/".
topic := "highScores"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Topic: topic,
}
// Send a message to the devices subscribed to the provided topic.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// The topic name can be optionally prefixed with "/topics/".
var topic = "highScores";
// See documentation on defining a message payload.
var message = new Message()
{
Data = new Dictionary<string, string>()
{
{ "score", "850" },
{ "time", "2:45" },
},
Topic = topic,
};
// Send a message to the devices subscribed to the provided topic.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"topic" : "foo-bar",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
cURL 명령어:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"message": {
"topic" : "foo-bar",
"notification": {
"body": "This is a Firebase Cloud Messaging Topic Message!",
"title": "FCM Message"
}
}
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
여러 주제를 조합하여 메시지를 보내려면 대상 주제를 지정하는 불리언 표현식인 조건을 지정합니다. 예를 들어 다음 조건은 TopicA
와 함께 TopicB
또는 TopicC
를 구독하는 기기로 메시지를 전송합니다.
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
FCM은 괄호 안의 조건부터 모두 판정한 후 왼쪽에서 오른쪽으로 표현식을 판정합니다. 위 표현식에서 주제를 하나만 구독한 사용자는 메시지를 수신하지 않습니다. TopicA
를 구독하지 않은 사용자도 메시지를 수신하지 않습니다. 다음과 같은 조합으로 구독해야 메시지를 수신합니다.
TopicA
,TopicB
TopicA
,TopicC
조건식에 최대 5개의 주제를 포함할 수 있습니다.
조건으로 보내는 방법은 다음과 같습니다.
Node.js
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
const condition = '\'stock-GOOG\' in topics || \'industry-tech\' in topics';
// See documentation on defining a message payload.
const message = {
notification: {
title: '$FooCorp up 1.43% on the day',
body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
},
condition: condition
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
자바
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
String condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("$GOOG up 1.43% on the day")
.setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.")
.build())
.setCondition(condition)
.build();
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# Define a condition which will send to devices which are subscribed
# to either the Google stock or the tech industry topics.
condition = "'stock-GOOG' in topics || 'industry-tech' in topics"
# See documentation on defining a message payload.
message = messaging.Message(
notification=messaging.Notification(
title='$GOOG up 1.43% on the day',
body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',
),
condition=condition,
)
# Send a message to devices subscribed to the combination of topics
# specified by the provided condition.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
condition := "'stock-GOOG' in topics || 'industry-tech' in topics"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Condition: condition,
}
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
var condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
var message = new Message()
{
Notification = new Notification()
{
Title = "$GOOG up 1.43% on the day",
Body = "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",
},
Condition = condition,
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"condition": "'dogs' in topics || 'cats' in topics",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message",
}
}
}
cURL 명령어:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"notification": {
"title": "FCM Message",
"body": "This is a Firebase Cloud Messaging Topic Message!",
},
"condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
다음 단계
- 서버를 사용하여 클라이언트 앱 인스턴스에서 주제를 구독하고 기타 관리 작업을 수행할 수 있습니다. 서버에서 주제 구독 관리를 참조하세요.