상세 컨텐츠

본문 제목

[Android] FCM PUSH Notification (FCM PUSH 알림)

PROGRAMMING/Mobile

by koharin 2022. 3. 9. 01:39

본문

728x90
반응형

프로젝트에서 클라이언트 앱에 띄울 알림 기능을 구현해야 해서 FCM 을 처음 사용해보았다.
원하는 기능은 클라이언트 앱에서 특정 이벤트가 발생했을 때 특정 사용자에게 알림을 보내는건데, Firebase Console (GUI)에서 이것저것 해보았지만 해당 기능을 구현하기엔 부족했다.
콘솔 GUI에서 알림을 보내는 기능은 전체 사용자나 특정 그룹 대상으로 공지나 이벤트 등을 알려야 할 때 유용할 것 같다.
공식 문서도 참고해보고 구글링으로 참고를 해보았는데, 공식문서는 뭔가 친절하지는 않고 구글링에서는 내가 구현하는 기능 예시를 찾기 어려웠다.
결과적으로는 공식 문서와 Firebase Github 예시 코드를 참고해서 원하는 기능을 구현할 수 있었다.


어떻게 Spring 서버에서 안드로이드로 알림을 보내는 기능을 구현했는지 정리하고자 글을 써본다.
이 글은 서버에서 보낸 알림 메시지를 안드로이드에서 처리하는 방법에 대한 내용이다.

 

📝 Firebase Cloud Messaging (FCM)

  • GCM (Google Cloud Messaging) 후속 버전
  • 타겟 디바이스에 알림은 Console GUI, Admin SDK, HTTP/XMPP의 세 가지 방법이 있다.
  • 클라이언트의 백엔드 서버에서 알림을 보내는 기능을 구현해야 하므로 Admin SDK 를 사용했다.


FCM 메시지 종류
메시지는 알림 메시지, 데이터 메시지의 2가지 종류가 있다.
알림과 데이터 페이로드가 모두 포함된 메시지는 알림 메시지로 분류된다.
다음은 알림 메시지, 데이터 메시지의 예시 형태다.

{
  "message":{
    "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "notification":{
      "title":"Portugal vs. Denmark",
      "body":"great match!"
    }
  }
}
  • 알림 메시지

 

{
  "message":{
    "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "data":{
      "Nick" : "Mario",
      "body" : "great match!",
      "Room" : "PortugalVSDenmark"
    }
  }
}
  • 데이터 메시지

 

{
  "message":{
    "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "notification":{
      "title":"Portugal vs. Denmark",
      "body":"great match!"
    },
    "data" : {
      "Nick" : "Mario",
      "Room" : "PortugalVSDenmark"
    }
  }
}
  • 알림 메시지 - 데이터 페이로드를 포함한 메시지


메시지를 클릭했을 때 보여줄 정보는 사용자에 따라 다르기 때문에 데이터 페이로드를 포함해서 알림 메시지를 사용했다.
데이터는 key, value가 모두 String인 맵으로, key와 value를 지정해서 메시지에 넣어준다.


 

📝 Firebase Console에서 프로젝트 생성

서버에서 FCM PUSH 메시지를 생성해서 보내기 위해서는 Firebase Console에 프로젝트가 있어야 한다.
클라이언트인 안드로이드에서도 필요하므로 앞으로의 과정을 위해 프로젝트를 생성해주자.

  • 앱에 Firebase를 추가한다.
  • PUSH 메시지를 받는 클라이언트 앱인 안드로이드를 선택해준다.

 

  • 앱 패키지명, SHA-1을 입력해준다.
  • 어떤 앱으로 보내는지 명시해주는 과정이다.

 

  • google-services.json 구성 파일을 받아서 app 루트 경로에 추가해준다.



 

📝 안드로이드 설정 (Frontend)

Google Play Services

  • 클라이언트가 안드로이드이므로, 안드로이드 스튜디오 SDK Manager - SDK Tools에서 Google Play Services를 설치한다.

 

AndroidManifest.xml

<service
	android:name=".api.MessagingService"
    android:enabled="true"
    android:exported="false" >
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
  • FirebaeMessagingService를 상속받는 서비스를 추가한다.
  • 백그라운드 앱 알림 수신, 포그라운드 앱의 알림 수신, 데이터 페이로드 수신, 업스트림 메시지 전송 등을 수행하려면 이 FirebaeMessagingService 서비스를 확장해야 한다.
  • 서버에서 보낸 메시지를 클라이언트에서 받으면, FirebaeMessagingService 서비스의 onMessageReceived가 호출되어 메시지를 받을 수 있고, 해당 메시지를 Notification으로 알림을 띄워주는 기능을 구현하여 클라이언트에서 알림을 띄우면 된다.
<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/default_notification_channel_id" />
  • 알림을 띄우려면 Channel을 사용해야 하기 때문에 channel id도 meta-data로 추가해준다.

 

build.gradle (app)

implementation 'com.google.firebase:firebase-messaging-ktx:23.0.0'
implementation 'com.google.firebase:firebase-bom:29.1.0'
implementation 'com.google.firebase:firebase-core:20.1.0'
  • Firebase Cloud Messaging 사용 위해 위의 내용을 추가해준다.



반응형

📝 안드로이드 메시지 수신 구현

다음은 FirebaseMessagingService 서비스를 상속받는 서비스에서 구현하는 내용이다.

FCM registration token 생성

  • FCM에서는 어떤 디바이스로 알림을 보내야 하는지 구분하기 위해 디바이스마다 FCM registration token을 발급해준다.
  • 앱이 처음 실행되면 자동으로 발급된다.
  • 서버에서는 FCM registration 토큰을 관리해주면서 알림을 특정 사용자에게 보내야 할 때 알림을 보내야 하는 사용자의 토큰을 가져와서 메시지를 보내주면 된다.
  • 서버에서는 사용자의 토큰이 변화될 때마다 갱신된 토큰을 저장해서 토큰의 신선도를 유지해줘야 한다.
    • 알림을 보내야 하는 사용자의 토큰이 변경된 상태에서 유효하지 않은 토큰으로 알림 전송 시 The registration token is not a valid FCM registration token 오류가 발생할 수 있기 때문

 

override fun onNewToken(token: String) {
        super.onNewToken(token)

        Log.d(TAG, "FCM token created: $token")

        // save updated fcm token to server
        LoginActivity().saveFCMToken{ result ->
            if(result == 200) Log.d("[SAVE FCM TOKEN]", "success")
            else Log.d("[SAVE FCM TOKEN]", "failed")
        }
}
  • FirebaseMessagingService 서비스의 onNewToken 메소드는 토큰 갱신 시 호출되므로, 이때 서버로 업데이트된 토큰을 보내준다.

 

FCM registration token 사용

// get current registered token
    fun getToken(): String?{
        var token: String? = null
        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
            if(!task.isSuccessful){
                Log.w(TAG, "Fetching FCM registration token failed", task.exception)
                return@OnCompleteListener
            }
            // get new FCM registration token
            token = task.result
            Log.d(TAG, "FCM token: $token")
        })
        return token
}
  • 사용자 토큰은 FirebaseMessaging의 getInstance 메소드에서 token을 가져올 수 있다.

 

메시지 수신

  • 앱 상태가 포그라운드, 백그라운드에 상관없이 알림이 오면 onMessageReceived 메소드에서 데이터가 처리된다.

 

override fun onMessageReceived(remoteMessage:  RemoteMessage) {
        super.onMessageReceived(remoteMessage)

        val from = remoteMessage.from!!
        val title = remoteMessage.notification?.title
        val body = remoteMessage.notification?.body
        val requestId = Integer.parseInt(remoteMessage.data["requestId"])

        Log.d(TAG, "message received: $remoteMessage")
        Log.d(TAG, "from: $from, title: $title, body: $body, data: $requestId")

        sendNotification(title!!, body!!, requestId)
}
  • 안드로이드에서 메시지가 수신되면 onMessageReceived 메소드의 RemoteMessage에서 받는다.
  • 이후 RemoteMessage에서 받은 notification 정보(title, body 등)와 데이터 정보로 안드로이드에서 알림을 띄울 Notification을 구현할 수 있다.



 

📝 안드로이드 알림 구현

private fun sendNotification(title: String, text: String, requestId: Int){
      
        val intent = Intent(this, CardRequestActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_CLEAR_TOP and Intent.FLAG_ACTIVITY_NEW_TASK
        }
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, FLAG_MUTABLE)

        val channelId = getString(R.string.notification_channel_id)
        val channelName = getString(R.string.default_notification_channel_name)
        val defaultSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setAutoCancel(true)
            .setSound(defaultSound)
            .setContentText(text)
            .setContentTitle(title)
            .setContentIntent(pendingIntent)
            .setSmallIcon(R.drawable.bling)
        // create notification channel
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
        notificationManager.createNotificationChannel(channel)
        notificationManager.notify(0, notificationBuilder.build())
    }
}
  • PendingIntent 알림을 클릭했을 때 보여줄 Intent 설정
  • NotificationCompat.Builder로 channel을 지정하여 title, body, PendingIntent, icon 등으로 알림을 구성하고 NotificationChannelm NotificationManager로 채널을 구성한다.
  • 이후 구성한 알림을 NotificationManager의 notify 메소드로 띄울 수 있다.

 

메시지를 받으면 messagId를 응답으로 받게 되고,
어느 Firebase projectId에서 왔는지 (from), 알림 제목 (title), 알림 내용 (body), 데이터 내용 (data 맵의 key로 value 가져올 수 있다.)을 확인할 수 있다.
알림 띄워주는 부분에서 알림을 띄워주면 메시지에 있는 token을 가지는 디바이스로 알림이 간다.

728x90
반응형

관련글 더보기