본문 바로가기
Android/Firebase

[Android] FCM(Firebase Cloud Messaging)

by 태크민 2023. 9. 8.

FCM (Firebase Cloud Message)

FCM은 Firebase Cloud Messaging의 약자로, 무료로 메시지를 안정적으로 전송할 수 있는 크로스 플랫폼 메시징 솔루션이다.

클로스 플랫폼
: Cross + Platform의 합성어로,
"다양한 플랫폼에서 사용할 수 있는" 의미를 가지고 있다.

 

그렇다면 왜 앱에서는 FCM을 사용해서 Push 메시지를 보내는 것일까?

기존에는 iOS, Android, Web 등의 플랫폼에서 Push 메시지를 보내기 위해서는 각 플랫폼 환경별로 개발해야 하는 불편함이 있었다. 하지만 FCM은 크로스 플랫폼 메시지 솔루션이기 때문에 FCM을 이용해서 개발을 진행하면, 플랫폼에 종속되지 않고 Push 메시지를 전송할 수 있다.

또한, 앱 서버에서 본연의 서버의 기능을 수행하면서 알림을 보내는 기능까지 포함된다면 서버의 속도는 처리량이 많아 느려질 것이다.

따라서, 본 서버는 알림 기능을 제공하는 서버(FCM서버)에 알림요청을 하는 구조로 쓰이며, FCM 서버가 이를 대신 해줌으로써 앱의 서버 단에서 직접 알림 기능 로직을 처리하지 않아도 된다. 

 

 


 

FCM 메시지 유형

1. 알림(Notification) 메시지

FCM이 클라이언트 앱을 대신하여 최종 사용자 기기에 자동으로 메시지 표시한다.

사용자에게 표시되는 키 모음이 사전 정의 되어 있다.

전송 방법

  • 앱 서버 및 FCM 서버 API 사용: notification 키를 설정, 선택사항으로 데이터 페이로드 추가 가능
  • 알림 콘솔 사용: 메시지 본문, 제목 등을 입력하고 전송, 알림 콘솔에서 맞춤 데이터를 제공하여 선택사항인 데이터 페이로드를 추가

앱이 백그라운드 상태

  • 푸쉬 메시지가 사용자 기기의 알림 목록으로 이동

앱이 포그라운드 상태

  • onMessageReceived()가 처리
  • notification 정보가 넘어오므로 앱에서 Notification 객체를 생성하여 알림을 표시 하여야함

 

2. 데이터(Data) 메시지

클라이언트 앱이 데이터 메시지 처리를 담당한다.

데이터 메시지에는 맞춤 Key-Value 쌍만 있음

전송 방법

  • 앱 서버 및 FCM 서버 API 사용: data 키만 설정

앱이 포그라운드 / 백그라운드 상태 

  • onMessageReceived()가 처리
  • Key-Value 쌍의 data 페이로드 형태로 넘어므로 앱에서 Notification 객체를 생성하여 알림을 표시하여야함

 


 

FCM 동작 메커니즘

크게 메커니즘의 구성요소는 송신자, FCM 백엔드 서버, 수신자로 구분한다.

  • 송신자는 주로 앱서버, Firebase Console GUI 등이 될 수 있다.
  • 수신자는 우리가 흔히 사용하는 iOS 또는 안드로이드 운영체제를 사용하는 모바일 기기가 될 수 있다.
  • FCM 백엔드 서버는 실질적으로 앱서버에로부터 요청을 받아서 메시지를 처리하는 서버가 해당이 된다.

 

앱에서 Push Token을 얻는 과정

  1. 모바일에 앱이 설치되는 순간 Firebase 서버에 Push Token 획득을 위한 요청을 보낸다.
  2. Firebase 서버에서 Push Token를 만들어 모바일에 전달
  3. 모바일 앱에 전달된 Push Token를 서버에 전송
  4. 서버는 전달받은 Push Token를 DB에 저장하여 타켓 모바일의 식별자로 사용

 

서버에서 메시지를 모바일에 전달하는 과정

  1. 앱 서버에서 메시지를 타겟 모바일에 전달하기 위해 DB에서 Push Token을 가져온다.
  2. 앱 서버는 FCM 백엔드 서버에 클라이언트 앱에 보내고자 하는 메시지 정보와 Push Token을 담아서 HTTP POST 방식으로 Request한다.
  3. FCM 백엔드 서버에서 전달받은 Push Token 값을 식별해 어떤 모바일의 어떤 앱인지를 식별한다.
  4. 식별된 모바일의 식별된 앱을 실행하여 메시지를 전달한다.

 

휴대폰이 꺼져있을 때는 FCM 전송이 될까?

안드로이드 의경우 기기가 꺼져있거나 FCM에서 앱이 과도한 리소스를 소비하고 수명에 부정적인 영향을 미치는 것을 방지하기 위해 의도적으로 메시지를 지연할 수 있다.

이러한 상황이 발생하면 FCM이 메시지를 저장한 후 전송할 수 있게 되면 바로 전송한다.

메시지의 수명은 최대 4주가 기본 값이며, 메시지의 최대 수명을 지정할 수도 있다.

 

 


 

FCM 예제

1. Firebase 연동

1-1. 안드로이드 스튜디오 - Tools - Firebase 클릭

 

1-2. Cloud Messaging - Set up Firebase Cloud Messaging 클릭

이 중 Cloud Messaging을 찾아서 클릭하고 파란 글씨를 클릭해준다.

 

1-3. "Connect to Firebase" 클릭

이렇게 친절하게 해야 하는 순서가 번호로 매겨져 있고

버튼을 누르면 gradle에 추가하고 하는 작업들을 자동으로 완성해준다.

 

 

1-4. 구글 계정 로그인

그러면 파이어 베이스와 앱을 연결하기 위해 구글 로그인 창으로 넘어간다.

계정 선택하고 다시 안드로이드 스튜디오로 돌아오면 된다.

 

1-5. "Connect to Firebase" 클릭

 

1-6. "Add FCM to your app" 클릭

Add FCM to your app을 누르면 자동으로 gradle이 추가된다.

시간이 약간 걸리므로 Connected 뜰 때까지 좀 기다리면 된다.

 

 

2. 코드 작성

1. AndroidManifest.xml

파이어베이스 서비스를 연동

        <service android:name=".FirebaseMessagingService"
            android:exported="true">
            <intent-filter>

                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>

        </service>

 

2. FirebaseMessagingService

class MyFirebaseMessagingService : FirebaseMessagingService() {
 
    private val TAG = "FirebaseService"
 
    // FirebaseInstanceIdService는 이제 사라짐. 이제 이걸 사용함
    override fun onNewToken(token: String?) {
        Log.d(TAG, "new Token: $token")
 
        // 토큰 값을 따로 저장해둔다.
        val pref = this.getSharedPreferences("token", Context.MODE_PRIVATE)
        val editor = pref.edit()
        editor.putString("token", token).apply()
        editor.commit()
 
        Log.i("로그: ", "성공적으로 토큰을 저장함")
    }
 
    override fun onMessageReceived(remoteMessage: RemoteMessage?) {
        Log.d(TAG, "From: " + remoteMessage!!.from)
 
        // Notification 메시지를 수신할 경우는
        // remoteMessage.notification?.body!! 여기에 내용이 저장되어있다.
        // Log.d(TAG, "Notification Message Body: " + remoteMessage.notification?.body!!)
 
        if(remoteMessage.data.isNotEmpty()){
            Log.i("바디: ", remoteMessage.data["body"].toString())
            Log.i("타이틀: ", remoteMessage.data["title"].toString())
            sendNotification(remoteMessage)
        }
 
        else {
            Log.i("수신에러: ", "data가 비어있습니다. 메시지를 수신하지 못했습니다.")
            Log.i("data값: ", remoteMessage.data.toString())
        }
    }
 
    private fun sendNotification(remoteMessage: RemoteMessage) {
        // RequestCode, Id를 고유값으로 지정하여 알림이 개별 표시되도록 함
        val uniId: Int = (System.currentTimeMillis() / 7).toInt()
 
        // 일회용 PendingIntent
        // PendingIntent : Intent 의 실행 권한을 외부의 어플리케이션에게 위임한다.
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) // Activity Stack 을 경로만 남긴다. A-B-C-D-B => A-B
        val pendingIntent = PendingIntent.getActivity(this, uniId, intent, PendingIntent.FLAG_ONE_SHOT)
 
        // 알림 채널 이름
        val channelId = getString(R.string.firebase_notification_channel_id)
 
        // 알림 소리
        val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
 
        // 알림에 대한 UI 정보와 작업을 지정한다.
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.mipmap.ic_launcher) // 아이콘 설정
            .setContentTitle(remoteMessage.data["body"].toString()) // 제목
            .setContentText(remoteMessage.data["title"].toString()) // 메시지 내용
            .setAutoCancel(true)
            .setSound(soundUri) // 알림 소리
            .setContentIntent(pendingIntent) // 알림 실행 시 Intent
 
        val notificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
 
        // 오레오 버전 이후에는 채널이 필요하다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "Notice", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }
 
        // 알림 생성
        notificationManager.notify(uniId, notificationBuilder.build())
    }
}

onNewToken 토큰을 생성하는 메서드이다.

17.0.0 버전 이전에는 무슨 getInstance? refreshToken? 이런 메서드가 있어서

생성하고 토큰 업데이트하고 하는 로직을 작성해야 했던 것 같다.

근데 이제는 이 메서드를 쓰면 알아서 내부에서 관리해주도록 바뀐 모양이다.

현재 기기의 토큰 값을 가져오는 메서드가 분명히 있을 것 같은데

찾다가 화딱지 나서 그냥 토큰이 생성될 때 따로 저장을 하도록 하였다.

 

onMessageReceived 메시지를 수신하는 메서드이다.

메시지에 제목이나 내용이 들어있는지 검사하고

문제가 없다면 sendNotification을 호출한다.

 

sendNotification 알림을 생성하는 메서드이다.

아이콘은 어떻게 할 건지, 알림 소리는 어떻게 할건지 등

이런 세세한 옵션을 설정하고 알림을 생성하면 비로소 푸시 알림이 뜨게 되는 것이다.

 

3. Push 테스트

3-1. 파이어베이스 콘솔 홈페이지 접속

https://console.firebase.google.com/u/0/?hl=ko

이 중 자신이 생성한 프로젝트를 클릭한다.

 

3-2. Cloud Messaging - Send your firest message 클릭

 

 

3-3. 푸시알림 내용 설정

알림 제목과 알림 텍스트를 적고 다음을 눌러준다.

 

 

 

 

3-4. 앱 선택

앱 선택을 하고 "다음" 버튼을 누르면 Push메시지가 정상적으로 전송이 된다.

 

 

'Android > Firebase' 카테고리의 다른 글

[Android] Firebase Crashlytics 적용하기  (0) 2023.09.11
[Android] Firebase 란?  (0) 2023.09.08