본문 바로가기
Development/Android

[Android] Firebase - 6. FCM(Firebase Cloud Messaging) 으로 알림을 보내보자!

by du.it.ddu 2020. 12. 19.
반응형


모바일 앱을 사용하다 보면, 광고나 메시지 등의 알림이 상단에 배너처럼 띄워지거나 기기의 알림창에 아이콘이 떠있는 등을 쉽게 볼 수 있다.

카카오톡, 라인 등과 같은 메신저, 게임, 커머스 등에서도 아주 쉽게 확인할 수 있다.

이는 사용자에게 알림을 주기도 하지만 적절한 알림이나 광고를 통해 전체 혹은 특정 사용자의 행동을 유도하거나 앱으로의 유입을 위해 활용된다.

모바일에서 활용할 수 있는 핵심적인 기능 중 하나가 아닐까 싶다.

이를 FCM을 통해 구현해보도록 하자.


1. 문서를 꼭 참고하자!

FCM은 구현도 구현이지만, 내부 스펙에 대하여 알아야 할 내용이 꽤 있다. 따라서 공식문서를 꼭 읽어보자!

firebase.google.com/docs/cloud-messaging/?authuser=1#implementation_paths

 

Firebase 클라우드 메시징

Firebase 클라우드 메시징(FCM)은 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션입니다.

firebase.google.com

firebase.google.com/docs/cloud-messaging/android/client?authuser=1

 

Android에서 Firebase 클라우드 메시징 클라이언트 앱 설정

Firebase 클라우드 메시징 Android 클라이언트 앱을 만들려면 FirebaseMessaging API와 Gradle이 있는 Android 스튜디오 1.4 이상을 사용하세요. 이 페이지의 안내에서는 Android 프로젝트에 Firebase를 추가하는 단

firebase.google.com


2. Gradle 세팅

buildscript {

  repositories {
    google()
  }

  dependencies {
    ...
    classpath 'com.google.gms:google-services:4.3.4'
  }
}

allprojects {
  ...

  repositories {
    google()
    ...
  }
}

프로젝트 레벨의 build.gradle의 dependencies에 classpath 'com.google.gms:google-services:4.3.4' 를 추가한다.
repositories는 디폴트로 google이 있겠지만, 확인은 해보자.

plugins {
    ...
    id 'com.google.gms.google-services'
}

android {
    ...
}

dependencies {
    ...

    implementation platform('com.google.firebase:firebase-bom:26.1.1')
    implementation 'com.google.firebase:firebase-messaging-ktx'
    ... // other firebase services
}

앱 레벨의 gradle로 이동하여 'com.google.gms.google-services' 플러그인을 추가한다.
그리고 dependencies에 'com.google.firebase:firebase-messaging-ktx' 를 추가한다.


3. AndroidManifest.xml 세팅

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.duitdduandroid.codelabs.firebase">

    <application
        ...

        <service
            android:name=".fcm.MyFcmService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
        
        ...
        
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_icon"
            android:resource="@drawable/ic_launcher_foreground" />
        
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_color"
            android:resource="@color/white" />

        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/default_notification_channel_id" />
        
    </application>

</manifest>

FCM을 포그라운드와 백그라운드에서 처리하기 위해 서비스로 구현하기 때문에 "AndroidManifest.xml"에 서비스를 등록 해 주어야 한다.
서비스 클래스는 "FirebaseMessagingService"를 상속한 클래스이며, 이후 단계에서 구현할 것이다.
구현 후 위의 "name" 부분에 구현한 클래스를 넣어주면 된다.

그리고 선택적으로 노티피케이션의 아이콘 혹은 컬러를 meta-data를 통해 지정할 수 있다.

또한 Android 8.0부터는 노티피케이션 채널을 생성 해 주어야 하는데, Firebase에 디폴트 노티피케이션 채널을 지정해줄 수 있다. 물론 코드레벨에서 메시지의 타입에 따라 채널을 달리 해줄 수 있다.


3. FirebaseMessagingService 클래스 생성하기

class MyFcmService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        Log.d("MyFcmService", "New token :: $token")
        sendTokenToServer(token)
    }

    private fun sendTokenToServer(token: String) {
        // TODO :: TOKEN 값을 서버에 저장하자!
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // TODO :: 전달받은 리모트 메시지를 처리하자!
        
        Log.d("MyFcmService", "Notification Title :: ${remoteMessage.notification?.title}")
        Log.d("MyFcmService", "Notification Body :: ${remoteMessage.notification?.body}")
        Log.d("MyFcmService", "Notification Data :: ${remoteMessage.data}")
    }
}

우선 기본적인 뼈대는 위와 같다.
"FirebaseMessagingService" 를 상속하는 FCM 서비스 클래스를 만든다.

FCM을 사용하는 안드로이드 앱은 기기마다 고유한 토큰값을 발급받고 이 키를 통해 FCM을 받게 된다. 이 토큰값은 "onNewToken" 콜백에 의해 획득할 수 있으며, 이 값을 서버에 저장하고 서버는 이 토큰값으로 FCM을 보낸다.

참고로 토큰값은 앱 재설치, 앱 데이터 클리어 등의 행위로 인해 새로 발급될 수 있다.

앱이 FCM을 받으면 "onMessageReceived" 콜백에 의해 메시지 데이터를 얻을 수 있다.


4. FCM 토큰값을 확인하고 콘솔에서 메시지 보내보기

앱을 설치하고 실행하여 로그를 검색하면, 위와같이 토큰값을 확인할 수 있다.
저 값보다 훨씬 길지만 보안을 위해 잘랐다.

위 값을 복사하고 콘솔로 이동하여 "Send your first message"를 누른다. 처음이 아니라면 다른 화면이 보일 것이다.

위와같이 제목와 텍스트를 임의로 입력하자. 우측에서 각 플랫폼에 어떻게 보여지는지 임시로 확인할 수 있다.
콘솔상에서 특정 기기의 토큰을 사용하려면 "테스트 메시지 전송" 에서만 가능하다. 눌러보자.

위와 같은 팝업이 나올텐데, 아까 복사한 토큰값을 입력하여 추가하고 "테스트" 를 눌러보자.


# 앱이 포그라운드일 때

아마 위와 같은 로그를 확인할 수 있고, 알림은 나타나지 않았을 것이다.
포그라운드일 때는 직접 알림을 띄워주어야 한다.
Data는 비어있을텐데, 이는 FCM을 전송할 때 서버측에서 JSON 형태의 데이터를 넘겨주는 등이 필요하다.

# 앱이 백그라운드일 때

기기의 상태에 따라 진동 혹은 알림음이 나며 알림창에서 위와 같은 알림을 확인할 수 있다.
백그라운드일 때는 전송한 노티피케이션의 제목과 텍스트로 알림을 생성 해 준다.


5. 포그라운드에서 노티피케이션 처리하기

class MyFcmService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        Log.d("MyFcmService", "New token :: $token")
        sendTokenToServer(token)
    }

    private fun sendTokenToServer(token: String) {
        // TODO :: TOKEN 값을 서버에 저장하자!
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        ...
        remoteMessage.notification?.let {
            showNotification(it)
        }
    }

    private fun showNotification(notification: RemoteMessage.Notification) {
        val intent = Intent(this, MainActivity::class.java)
        val pIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

        val channelId = getString(R.string.my_notification_channel_id)

        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(notification.title)
            .setContentText(notification.body)
            .setContentIntent(pIntent)

        getSystemService(NotificationManager::class.java).run {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channel = NotificationChannel(channelId, "알림", NotificationManager.IMPORTANCE_HIGH)
                createNotificationChannel(channel)
            }

            notify(Date().time.toInt(), notificationBuilder.build())
        }
    }
}

순서는 간단하다.

1. 알림(노티피케이션)을 누를 시 이동할 화면을 정하여 Intent 객체를 생성한다.
2. 이 인텐트는 당장 실행되는 것이 아니라 지연되므로 PendingIntent를 생성하여 위 Intent를 담는다.
3. NotificationCompat.Builder를 사용할 채널 id 값을 전달하여 생성한다.
4. 우선순위와 아이콘, 제목, 내용, 그리고 PendingIntent를 전달한다.
5. NotificationManager 객체를 얻고, Android 8.0 이상인 경우 채널을 생성하도록 하고 알림을 띄운다.

Android 8.0 부터는 알림을 반드시 생성 해 주어야 한다. 반복적으로 수행할 필요는 없기 때문에 Application 레벨에서 한번만 생성한다던지, createNotificationChannel 전에 생성 유무를 확인하는 방법도 있다.

또한 알림을 받았을 때 상단에 배너처럼 띄워주기 위해선 setPriority를 통해 "PRIORITY_HIGI" 를 주어야 한다.

기종마다 다를테지만 위와 같은 Heads-Up Notification을 수신하게 된다.


서버와 연동하여 FCM을 더 활용하기 위해선 메시지의 내부에 데이터를 삽입해야 한다.

데이터를 백그라운드, 포그라운드에서 모두 접근하여 활용하려면 서버측에서 전송 시 노티피케이션 부분을 제거하고 데이터만 포함하도록 해야한다.

노티피케이션을 포함하면 백그라운드에 있을 때 서비스의 "onMessageReceived" 콜백이 호출되지 않고 알림을 눌러 앱 진입 시 첫 시작 액티비티의 intent.extras에 포함되기 때문에 처리가 곤란하기 때문이다.

이 포스팅은 FCM의 기본적인 구현 방법만을 다루었고 더 많은 스펙과 내용을 알아야 활용이 가능하므로, 공식 문서를 참고하여 적용해보는것을 권장한다.


반응형