FCM은,
Firebase Cloud Messaging 의 약자입니다.
그렇다면 Firebase Cloud Messaging이 뭐냐구요?
이게 무엇인지, 이게 무슨 말인지를 더 이해하기 위해 FCM 메시지가 어떤 식으로 작동되는 지를 보도록 하겠습니다.
1. 사용자가 모바일 기기에 앱을 설치하고 최초 실행 시에, 토큰을 얻기 위해 클라우드 서버에 토큰 요청을 보내고 이를 획득합니다.
2. 획득한 토큰을 서버로 전송하여 DB에 토큰값을 저장합니다.
즉, 토큰은 서버가 클라우드에 메시지 전송을 요청할 때 어디로 메시지를 보낼 것인지를 구분하기 위해 쓰이는 값입니다.
3. 서버에서 클라우드로 토큰과 메시지 데이터를 보내어 메시지 전송을 요청합니다.
4. 클라우드는 요청받은 메시지를 토큰에 해당하는 단말기로 전송합니다.
5. 앱이 실행중이 아니어도 리스너를 통해 메시지를 수신할 수 있습니다.
FCM을 사용하는 이유, 즉 FCM의 이점들은 다음과 같이 있어요.
이 이후부터의 내용은 Android 기준입니다!
FCM Console
에 접속 -> 앱 추가
선택 -> Android
선택SHA-1 키를 받는 방법은 다음과 같아요.
(버전: Android Studio Dolphin 2021.3.1)
오른쪽 Gradle
을 선택하고 (빨간 네모), 코끼리 모양 버튼을 선택합니다 (빨간 동그라미).
팝업창에 gradle singingReport
를 입력하면, 하단에서 SHA1 key를 얻을 수 있습니다.
SHA1 key를 얻은 후 app으로 바꿔주는 거 잊지마세요!
id 'com.google.gms.google-services'
추가 implementation platform('com.google.firebase:firebase-bom:31.1.1')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-messaging-ktx'
buildscript {
repositories {
google()
}
dependencies {
classpath 'com.google.gms:google-services:4.3.13'
}
}
위 코드를 추가해줍니다.
Google play services
검색 & 설치FirebaseMessagingService
를 상속받는 FCM 서비스 클래스 생성 import ...
import com.google.firebase.messaging.FirebaseMessagingService
import ...
class Service이름: FirebaseMessagingService() {
}
<application
... >
<service
android:name=".Service이름"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
...
</application>
val fcm = Intent(applicationContext, Service이름::class.java)
startService(fcm)
빌드
-> Cloud Messaging
를 선택해줍니다. 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
는 무엇일까요?
궁금증이 생겨 찾아보았습니다 -> 공식 링크 및 참고 링크
위의 링크들을 보았는데, 저는 여러 채널을 이용하지 않을 예정이라 우선 strings.xml에 임의의 아이디를 작성해주는 방식으로 진행했습니다.
PendingIntent
는 당장은 수행되진 않지만, 특정 시점이 되었을 때 실행되는 intent 입니다.
위에서는 notification을 선택했을 때 activity를 실행하기 위해 쓰이고 있어요.
pending intent를 생성하는 코드는 다음과 같습니다.
PendingIntent.getActivity(context, requestCode, intent, flag)
Pending Intent의 FLAG는 다음과 같이 있습니다.
그리고 현재 버전에서는 PendingIntent.MUTABLE , PendingIntent.IMMUTABLE 중 하나는 반드시 포함해야 돼요. (immutable 사용이 권장되고 있습니다.)
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