ANDROID/Android 앱 프로그래밍

[Android] FCM 푸시 알림(Notification)

주 녕 2022. 2. 7. 23:39
728x90

앞으로 개발하게 될 프로젝트에서 반복적이지만 간단한 푸시 알림 서비스를 사용하게 될 것 같아

개발 전 미리 FCM을 이용한 푸시 알림에 대해 공부하려고 포스팅을 작성하게 되었다.

 

FCM

Firebase 클라우드 메시징(FCM)은 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션입니다.
  • 알림 메시지 / 데이터 메시지 전송 
  • 다양한 메시지 타겟팅 : 단일 기기, 기기 그룹, 주제를 구독한 기기 등 3가지 방식으로 앱에 메시지를 배포할 수 있음
  • 클라이언트 앱에서 메시지 전송 : FCM의 신뢰성 높고 배터리 효율적인 연결 채널을 사용하여 메시지를 보낼 수 있음

 

FCM vs. AlarmManager vs. WorkManager

FCM : 네트워크 기반의 푸시 알람 서비스 

👍 네트워크만 연결되어 있다면 원하는 기능을 서버에서 구현한 뒤, 배포하면 되므로 많이 이용하고 있음

☝️ 오프라인 환경에서도 사용자에게 알람을 보내고 싶다면?...

 

AlarmManager : 시스템 알람 서비스

👍 앱 외부에서 작동하기 때문에 앱이 실행 중이 아닐 때도 이벤트를 트리거할 수 있음

☝️ Doze 모드에서는 알람이 울리지 않는다...

 

WorkManager : 백그라운드 작업 라이브러리

👍 Doze 모드, 배터리 세이버 등의 제안 사항 속에서 안전하게 백그라운드 작업을 할 수 있음

☝️ ...없다?

 

 

FCM이 메세지를 보내는 과정

  1. FCM 서버에 Token 요청 & 획득
  2. 앱 서버에 Token 저장 (서버가 FCM 서버에 메시지 전송을 요청할 때 어디로 보낼지 구분하는 용도)
  3. Token을 활용하여 메세지 전송 요청 (앱 서버 ----------- Token & 메세지 데이터 -----------> FCM 서버)
  4. 메세지 전송 (FCM 서버는 요청받은 메세지를 Token에 해당하는 디바이스에 전송)
  5. 메세지 수신

 

기존 Firebase의 프로젝트 설정과 동일하므로 앞의 과정은 생략한다.

build.gradle

dependencies {
    ...
    implementation 'com.google.firebase:firebase-messaging-ktx'
}

 

AndroidManifest.xml

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

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        ...>
        <activity android:name=".ui.LoginActivity"
            android:exported="true">
            ...
        </activity>

        <service android:name=".MessagingService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
    </application>

</manifest>
  • 네트워크 기반의 서비스이기 때문에 인터넷 퍼미션을 추가함
  • 아래의 구현한 서비스를 등록하기 위해 <service> 
  • 메세지를 수신하기 위해 <intent-filter> 

 

MessagingService.kt

class MessagingService : FirebaseMessagingService() {
    
    // 수신한 메세지 처리
    override fun onMessageReceived(remoteMsg: RemoteMessage) {
        super.onMessageReceived(remoteMsg)
        if (remoteMsg.notification != null) showNotification(remoteMsg.notification?.title, remoteMsg.notification!!.body!!)
    }
    
    // Token 처리
    override fun onNewToken(token: String) {
        super.onNewToken(token)

    }

    private fun showNotification(title: String?, body: String) {
        ...
    }
}
  • onNewToken() : FCM 서버에 등록되었을 때 호출됨 
  • onMessageReceived() : FCM 서버에 메세지를 전송하면 자동으로 호출되며, 메서드 내의 메세지를 처리할 수 있음

테스트는 Firebase Console에서 할 수 있음

  • 알림 제목, 알림 텍스트는 onMessageReceived의 파라미터인 RemoteMessage에 전달되어 get으로 사용할 수 있음
  • '다음' 버튼을 눌러 현재 앱을 선택하고 '검토' 버튼을 눌러 바로 메세지를 보낼 수 있음

테스트 푸시 알람

 

 

Notification

이 포스팅에서는 기본 알림만 다룬다.

공식 문서 : 기본 알림 이미지

알림 콘텐츠

NotificationCompat.Builder 객체를 사용하여 알림 콘텐츠와 채널을 설정함

  • setSmallIcon() : 아이콘
  • setContentTitle() : 알림 제목 텍스트
  • setContentText() : 알림 본문 텍스트
  • setPriority() : 알림의 우선순위 (Android 8.0 이상에서는 채널 중요도를 대신 설정해야 함)
  • setStyle() : 알림을 더 길게 설정하려면 스타일 템플릿을 추가하여 확장 가능한 알림으로 설정

이때 생성자에서 채널 ID를 제공해야 함 (Android 8.0 이상에서는 호환성을 유지하기 위해 필요함)

    var builder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.notification_icon)
            .setContentTitle(textTitle)
            .setContentText(textContent)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)

 

채널 만들기

Android 8.0 이상(SDK_INT >= VERSION_CODES.O)에서 알림을 제공하기 위한 방법

→ NotificationChannel 인스턴스를  createNotificationChannel()에 전달하여 앱의 알림 채널을 시스템에 등록

  • 채널 ID
  • 채널 이름
  • 채널 중요도 : NotificationManager에서 상수 지원
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = getString(R.string.channel_name)
            val descriptionText = getString(R.string.channel_description)
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
                description = descriptionText
            }
            // Register the channel with the system
            val notificationManager: NotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }

 

알림 탭 설정

모든 알림은 앱에서 알림에 관련된 Activity를 열려면 탭에 응답해야 함

→ PendingIntent 객체로 정의된 콘텐츠 인텐트를 지정하여 setContentIntent()에 전달해야 함

  1. 어떤 화면으로 이동할 것인지 Intent 생성
  2. PendingIntent를 위에서 생성한 Intent 인스턴스를 매개변수로 하여 생성
  3. NotificationCompat.Builder에 setContentIntent(pendingIntent)로 PendingIntent 추가
    val intent = Intent(this, AlertDetails::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
    }
    val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)

    val builder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.notification_icon)
            .setContentTitle("My notification")
            .setContentText("Hello World!")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            // Set the intent that will fire when the user taps the notification
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)

*Intent 생성 시, flag를 설정하면 사용자가 알림을 통해 앱을 연 뒤에 예상되는 탐색 환경 유지에 도움이 됨

  • 알림의 응답에만 존재하는 Activity → 앱의 기존 작업 및 백 스택에 추가되는 대신 새 작업을 시작
  • 앱의 일반 앱 흐름에 존재하는 Activity → 백 스택이 생성되어 뒤로 및 위로 버튼에 대한 사용자의 기대가 유지
 

[Android] 플래그(flag)와 부가 데이터(Extra Data)

모든 내용은 Do it! 안드로이드 앱 프로그래밍을 바탕으로 정리한 것입니다. 액티비티로 만든 화면이 한 번 메모리에 만들어졌는데도 계속 startActivity()나 startActivityForResult() 메서드를 여러 번 호

junyoung-developer.tistory.com

*setAutoCancel()은 사용자가 알림을 탭하면 자동으로 알림을 삭제함 (NotificationCompat.Builder에 추가)

 

알림 표시

NotificationManagerCompat.notify()를 호출하여 아래 2개의 값을 전달

  • 알림의 고유 ID
  • NotificationCompat.Builder.build()의 결과
    with(NotificationManagerCompat.from(this)) {
        notify(notificationId, builder.build())
    }

 

MessagingService.kt

    private fun showNotification(title: String?, body: String) {
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT)

        val channelId = "book_channel"
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setContentTitle(title)
            .setContentText(body)
            .setAutoCancel(true)
            .setContentIntent(pendingIntent)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "book channel", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        notificationManager.notify(0, notificationBuilder.build())
    }

 

 

아직 프로젝트 개발 설계가 이루어지지는 않았지만, 고민을 해보니 개발할 서비스에서 굳이 FCM을 사용할 필요가 없다고 느껴졌다.

개인한테 사용을 독려하기 위한 알람이기 때문에 오히려 WorkManager을 이용해서 주기적으로 알람을 보내는게 더 낫지 않을까? 🤔


reference >

 

728x90