FCM

Kim, Sujin·2023년 2월 13일
1
post-thumbnail

What is FCM?

FCM은,
Firebase Cloud Messaging 의 약자입니다.
그렇다면 Firebase Cloud Messaging이 뭐냐구요?

  • 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션
  • 사용자에게 개별적으로, 혹은 그룹을 지어 메시지를 전송 가능하다.

이게 무엇인지, 이게 무슨 말인지를 더 이해하기 위해 FCM 메시지가 어떤 식으로 작동되는 지를 보도록 하겠습니다.


1. 사용자가 모바일 기기에 앱을 설치하고 최초 실행 시에, 토큰을 얻기 위해 클라우드 서버에 토큰 요청을 보내고 이를 획득합니다.
2. 획득한 토큰을 서버로 전송하여 DB에 토큰값을 저장합니다.
즉, 토큰은 서버가 클라우드에 메시지 전송을 요청할 때 어디로 메시지를 보낼 것인지를 구분하기 위해 쓰이는 값입니다.
3. 서버에서 클라우드로 토큰과 메시지 데이터를 보내어 메시지 전송을 요청합니다.
4. 클라우드는 요청받은 메시지를 토큰에 해당하는 단말기로 전송합니다.
5. 앱이 실행중이 아니어도 리스너를 통해 메시지를 수신할 수 있습니다.

Why FCM?

FCM을 사용하는 이유, 즉 FCM의 이점들은 다음과 같이 있어요.

  • FCM은 교차 플랫폼 메시징 솔루션입니다. 즉, Android, iOS 등의 플랫폼에 종속되지 않고 메시지를 전송할 수 있습니다.
  • 실시간 메시지를 받기 위해 계속 접속 상태를 유지하다 보면 배터리와 네트워크의 사용 과다 등으로 인해 문제가 발생할 수 있습니다.
    그러나 중간에 클라우드 메시지 서버를 둠으로써, 낮은 배터리와 네트워크 사용만으로 메시지의 실시간 송수신 처리가 가능합니다.

이 이후부터의 내용은 Android 기준입니다!

Start FCM

처음 접속

  • FCM Console에 접속 -> 앱 추가 선택 -> Android 선택
  • 패키지 이름, 앱 이름, SHA-1키를 입력

how to get SHA-1

SHA-1 키를 받는 방법은 다음과 같아요.
(버전: Android Studio Dolphin 2021.3.1)

  • 오른쪽 Gradle을 선택하고 (빨간 네모), 코끼리 모양 버튼을 선택합니다 (빨간 동그라미).

  • 팝업창에 gradle singingReport 를 입력하면, 하단에서 SHA1 key를 얻을 수 있습니다.

  • SHA1 key를 얻은 후 app으로 바꿔주는 거 잊지마세요!

google-services.json 파일 추가

  • app 하위 디렉토리에 추가해야 합니다.
    보기 방식을 project로 두고 하면 편해요 ~

SDK, gradle 설정

gradle (app)

  • plugins에 id 'com.google.gms.google-services' 추가
  • dependencies에 다음 코드 추가
    implementation platform('com.google.firebase:firebase-bom:31.1.1')
	implementation 'com.google.firebase:firebase-analytics-ktx'
	implementation 'com.google.firebase:firebase-messaging-ktx'

gradle (project)

  • 제가 사용한 안드로이드 스튜디오 버전(Dolphin 2021.3.1)에서는 project gradle의 구조가 바뀌어서,
    Firebase Console에 안내되어 있는 부분과는 조금 달랐습니다. 하지만 당황하지 않고 해주시면 돼요!
buildscript {
    repositories {
        google()
    }
    dependencies {
        classpath 'com.google.gms:google-services:4.3.13'
    }
}

위 코드를 추가해줍니다.

Google Play Services 설치

  • SDK Manager -> SDK Tools 에서 Google play services 검색 & 설치
  • FirebaseMessagingService를 상속받는 FCM 서비스 클래스 생성
    import ...
	import com.google.firebase.messaging.FirebaseMessagingService
	import ...

	class Service이름: FirebaseMessagingService() {
    
	}
  • Manifest에 서비스 등록
<application
	...  >

	<service
		android:name=".Service이름"
    	android:exported="false">
    <intent-filter>
		<action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
	</service>
	
	...

</application>
  • Activity에서 서비스 시작 (호출하기)
val fcm = Intent(applicationContext, Service이름::class.java)
startService(fcm)

FCM Test

  1. Firebase Console에 접속하여 빌드 -> Cloud Messaging 를 선택해줍니다.
  2. 캠페인 만들기를 누르고, Firebase 알림 메시지를 선택합니다.
  3. 메시지를 작성한 후 테스트 메시지를 전송합니다. (제목(title), 텍스트(body))
  4. FCM 등록 토큰 추가에서 토큰값을 추가합니다.
  • 이 때 token을 알기 위한 방법은 아래 두 가지가 있어요.
    (1) 앱 최초 실행 시 생성되는 토큰값을 불러오기
	override fun onNewToken(token: String) {
  		// token이 생성될 때 호출되는 함수
		// token이 생성되는 경우: 앱을 (재)설치 후 첫 실행될 때
		Log.d(TAG, "Token created: $token")
	}
(2) FirebaseMessaging의 getInstance로 토큰값 가져오기
	fun getToken(): String? {
    	var token: String? = null

    	FirebaseMessaging.getInstance().token
        	.addOnCompleteListener(OnCompleteListener { task ->
            	if(!task.isSuccessful) {
                	return@OnCompleteListener
            	}
            	token = task.result
            	Log.d(TAG, "Token: $token")
    	})
	    return token
	}    

메세지 수신하기


background 에서는 별도의 작업 없이 알림이 잘 오지만,
foreground에서도 알람을 받기 위해서는 어떻게 해야 할까요?
-> 받은 알람 데이터를 처리하는 onMessageReceived() method에서 notification을 만들어줍니다.

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

private fun showNotification(notification: RemoteMessage.Notification) {
    val intent = Intent(this, MainActivity::class.java)
		// PendingIntent: 노티를 터치했을 때 액티비티를 실행하기 위함
    val pIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) 

    val channelId = getString(R.string.fcm_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())     // 노티 등록
    }
}

channelId

그런데, 위의 코드에 나오는 channelId는 무엇일까요?
궁금증이 생겨 찾아보았습니다 -> 공식 링크참고 링크
위의 링크들을 보았는데, 저는 여러 채널을 이용하지 않을 예정이라 우선 strings.xml에 임의의 아이디를 작성해주는 방식으로 진행했습니다.

PendingIntent

PendingIntent는 당장은 수행되진 않지만, 특정 시점이 되었을 때 실행되는 intent 입니다.
위에서는 notification을 선택했을 때 activity를 실행하기 위해 쓰이고 있어요.

pending intent를 생성하는 코드는 다음과 같습니다.
PendingIntent.getActivity(context, requestCode, intent, flag)

FLAG

Pending Intent의 FLAG는 다음과 같이 있습니다.

그리고 현재 버전에서는 PendingIntent.MUTABLE , PendingIntent.IMMUTABLE 중 하나는 반드시 포함해야 돼요. (immutable 사용이 권장되고 있습니다.)

requestCode

PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
에서 0 이 requestCode입니다.
requestCode는 pending intent를 가져올 때 이를 구분하기 위한 고유 코드값이에요.

보통 request code를 0으로 많이 작성하는데, 저는 웹앱 개발 과정에서 다음과 같은 문제가 발생했었어요.

foreground 상태에서 fcm 메시지가 연달아 도착했을 때, 각각을 클릭하면 각각의 다른 페이지가 아닌 똑같은 페이지로 이동된다.

peding intent가 중복되는 문제였습니다.
이 때 requestCode를 0이 아니라 (int) System.currentTimeMillis()를 줌으로써 동일한 값이 들어가지 않도록 했더니 해결되었답니다 :)


참고자료

https://donghun.dev/Firebase-Cloud-Messaging
https://firebase.google.com/docs/cloud-messaging?hl=ko
https://doitddo.tistory.com/108
https://velog.io/@haero_kim/Android-PendingIntent-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0
https://developer88.tistory.com/187

0개의 댓글