본문 바로가기
별밤 일지/개발

[Firebase Cloud Messaging]Android FCM 적용

by 별밤 에디터 2024. 1. 21.

개요 및 설명

핸드폰 앱에서 푸시 알림 기능은 기본 중의 기본이다.

댓글 작성 알림, 공지 사항 알림, 업데이트 알림 등등 하나의 앱에서도 날아오는 알림의 종류는 매우 다양하다.

사실 첫 업데이트 때 구현해야 할 만큼 중요한 기능임에도 개발자의 역량 부족으로 1.3v에서 구현하게 되었다…(이렇게 성장하는 거지)

푸시 알림 기능에는 OneSignal, WebSocket 같은 다양한 방법들이 있지만, FCM을 선택하게 되었다.


FCM

FCM의 정의를 Firebase 공식 문서를 참고하면 다음과 같다.

‘Firebase Cloud Messaging은 메시지를 안정적으로 무료 전송할 수 있는 크로스 플랫폼 메세징 솔루션입니다. ‘

즉 FCM은 Google Firebase에 등록되어 있는 클라이언트 앱에 푸시 알림 기능을 무료로 제공해 주는 서비스이다.

FCM을 선택한 이유는 다음과 같은 장점들이 있기 때문이었다.

  1. iOS, Android, Flutter, C++, Unity 모두 호환이 가능하다.
    (별 헤는 밤은 다음 업데이트에 Flutter로 변환할 계획을 갖고 있기 때문에 매우 중요한 조건이었다.)
  2. 2.Cloud Messaging 서버를 중간에 두어, 사용자는 적은 자원(네트워크, 배터리)만으로 푸쉬 알림 기능을 이용할 수 있다.
  3. FCM SDK 설정만 하면 이용이 가능하기 때문에 알림 기능 구현의 시간과 비용을 줄일 수 있다.
    (우리 앱은 이미 Firebase console에 등록한 애플리케이션이었기 때문에 더욱 더 활용을 하지 않을 이유가 없었다.)

 

FCM 알림 서비스는 이미 많은 사람들이 구현을 해놓았기 때문에 상세히 구현 방법을 설명하기 보다 구현 과정에서 내가 많이 애먹은 파트들만 정리하도록 하겠다.

 


서버

Notification vs Message

 

FCM 서비스는 Messge안에 Notification과 data들로 이루어져 있고, Notification은 알림 제목(title), 알림 내용(body)을 포함하고, data에는 다양한 요소들을 추가할 수 있다.

 
public void sendMessageTo(String targetToken, String title ,String body) 
throws  FirebaseMessagingException{
        Notification notification = Notification.builder()
            .setTitle(title)
            .setBody(body)
            .build();
        Message message = Message.builder()
            .setToken(targetToken)
						.setNotification(notification)
            .putData("click","alarm")//알림 클릭 기능을 위한 data
            .putData("isNotice","comment")//알림, 공지사항 구분을 위한 data
            .build();
        try{
            firebaseMessaging.send(message);
        }catch (FirebaseMessagingException e){
            e.printStackTrace();
        }
    }

 

위 코드는 특정 사용자 한 명에게 알림을 보내는 코드이다.

 

앱 실행 중에는 푸시 알림이 구현한 대로 기능이 실행 되었지만, 백그라운드 상태일 때는 알림이 오지 않았다.

꽤 오랜 시간 삽질을 한 결과 그 이유는 공식 사이트에서 알 수 있었다.

https://firebase.google.com/docs/cloud-messaging/android/receive?hl=ko

 

Android 앱에서 메시지 수신  |  Firebase 클라우드 메시징

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 Android 앱에서 메시지 수신 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. F

firebase.google.com

 

안드로이드에서는 백그라운드 상태에는 onMessageReceived 함수에서 Notification은 무시하고, data만 읽어온다.

따라서 제목과 내용을 포함한 Notification을 받을 수 없으니 알림이 보내지지 않았던 것이다.

 

해결책

 

따라서 문제가 되는 Notification을 제거하고, Data만으로 Message를 구성하여 문제를 해결할 수 있었다.

 

public void sendMessageTo(String targetToken, String title ,String body)
 throws  FirebaseMessagingException{
        Message message = Message.builder()
            .setToken(targetToken)
            .putData("click","alarm")
            .putData("title",title)
            .putData("body",body)
            .putData("isNotice","comment")
            .build();
        try{
            firebaseMessaging.send(message);
        }catch (FirebaseMessagingException e){
            e.printStackTrace();
        }
    }

 

위의 코드는 Message에 있던 Notification 관련 코드들을 모두 제거하고, Notification 내부에 있던 title, body를 Data로 추가한 코드이다.

이렇게 하여 애플리케이션이 백그라운드, 포그라운드 상태에서도 알림 기능이 실행될 수 있었다.

 


 

안드로이드

푸시 알림 터치 구현

 

생각보다 다들 알림 전송까지만 구현하고, 알림 터치 이벤트를 정리한 사람은 별로 없었다.

사실 위의 코드에서 힘들게 Message에 여러 가지 데이터들을 넣은 이유는 여기에 있다.

Firebase console에서는 clickAction이라는 데이터를 추가하여 클릭 이벤트를 생성할 수 있지만

'com.google.firebase:firebase-admin:9.1.1' 해당 버전에는 clickAction을 지원하지 않는다.

따라서 터치 이벤트를 안드로이드에서 구현을 했다.

 

   			//Message의 PutData에 포함된 데이터 읽어오기
        String title = remoteMessage.getData().get("title");
        String body = remoteMessage.getData().get("body");
				//body가 HTML 코드로 구현
        CharSequence htmlBody = Html.fromHtml(body,HtmlCompat.FROM_HTML_MODE_LEGACY);
				//공지인지 아닌지 구분하기 위해 추가
        String isNotice = remoteMessage.getData().get("isNotice");
				//메인 화면으로 이동
        Intent intent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
				//푸시 알림 터치시 화면 이동
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 10 , intent, 
								PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);

        builder.setContentTitle(title)
                .setContentText(htmlBody)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setColor(ContextCompat.getColor(getApplicationContext(),R.color.point_blue))
                .setSmallIcon(R.drawable.push_notification_icon);

        Notification notification = builder.build();

 

onMessageReceived 코드에서 받아온 정보로 Notification을 구성하는 부분만 잘라서 가져왔다.

제일 중요한 코드는 PendingIntent 부분이다.

PendingIntent는 원하는 특정 시점에서 Intent를 실행시키는 함수로 Notification에서 많이 사용된다고 한다.

그리고 PendingIntent는 Flag를 추가하여 특성을 부여할 수 있는데, 내가 포함한 FLAG들의 특성은 다음과 같다.

-FLAG_IMMUTABLE: PendingIntent의 intent 변경 불가능, 다른 애플리케이션에서 수정하지 못하게 하기 위해 추가했다.

-FLAG_UPDATE_CURRENT: 이미 시스템에 PendingIntent가 존재한다면, ExtraData만 업데이트한다.
IMMUTABLE과 함께 사용하면 생성자만 PendingIntent를 수정할 수 있다.

 

여기서는 푸시 알림을 터치 시 Intent가 실행되어 MainActivity로 이동하고, MainActivity에서 Intent의 Extra를 읽어 알림 페이지로 이동하게 구현했다.


정리

이렇게 FCM 기능을 이용할 때 겪었던 어려움들에 대해 정리해 보았다.

백그라운드 상태에서 푸시 알림, 알림 터치 이벤트 둘 다 푸시 알림 구현할 때 당연하게 구현되어야 하는 부분들이지만 생각보다 구조가 많이 복잡했다.

나처럼 오랜 삽질을 겪지 않고 이 부분들을 구현하기를 바라며 글을 마친다.


 

<별 헤는 밤> 놀러가기 😊

 

별 헤는 밤: 밤하늘, 별자리, 여행정보와 날씨예보까지 - Google Play 앱

오늘부터 별잘알! 오늘밤 별자리 정보, 날씨·광공해·월령까지 고려한 '관측적합도', 주변 관측지 검색으로 누구나 쉽게 밤하늘을 즐겨보세요.

play.google.com