본문 바로가기

안드로이드

[안드로이드] FCM 서비스를 통해 메시지를 foreground, background에서 받을 경우 데이터 처리 방법 및 액티비티 이동

개요

파이어베이스의 FCM을 통해 안드로이드 애플리케이션으로 데이터를 전송함과 동시에 노티피케이션 클릭 시 특정 액티비티로 이동하는 방법을 설명한다. (FCM 세팅 방법 및 서버에서 FCM PUSH 방법 등은 제외)

 

1. 서버에서의 설정

click_action 속성을 추가한다. 이 속성은 시작하고 싶은 액티비티의 intent-filter로 활용하게 된다.

const message = {
      notification:{
           title:chat.nickname,
           body: chat.message,
        },
       data: {
	   createdAt: chat.createdAt,
	   message:"hello",
	   click_action: "NOTIFICATION_CLICK",
      },
      tokens: token
   };

 

 

2. Android의 AndroidManifest.xml에서의 설정

원하는 activity의 intent-filter에 서버에서 설정한 click_action 값을 설정한다.

<activity
    android:name=".ui.main.MainActivity"
    android:launchMode="singleTop">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <action android:name="NOTIFICATION_CLICK"/>
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
</activity>

 

3. 앱이 동작 중인 Foreground 상황에서의 Notifiaction 발생 및 클릭 시 액티비티로 데이터 전달

앱이 동작 중인 상황에서는 해당 서비스의 onMessageReceived(...) 메서드가 호출되어 이 때 적절한 동작을 처리하면 된다. 이를 위한 단계는 크게 5단계로 나뉜다.

  1. 받은 데이터 추출
  2. Intent 및 PendingIntent 생성
  3. Notification Channel 생성
  4. Notification 빌드
  5. Notification 알림

1. 받은 데이터 추출

이 단계를 비교적 쉽다. RemoteMessage 객체의 data 프로퍼티에서 get() 메서드를 통해 서버에서 전송한 메시지를 추출할 수 있다.

 override fun onMessageReceived(remoteMessage: RemoteMessage) {
     val createdAt = remoteMessage.data.get("createdAt")
     //...
 }

 

2. Intent 및 PendingIntent 생성

우리가 원하는 특정 액티비티에 데이터를 전달하면서 액티비티를 시작하기 위해 필요한 작업이다.

val intent = Intent(applicationContext,MainActivity::class.java)
intent.putExtra("createdAt", createdAt)

val pendingIntent = PendingIntent.getActivity(
	applicationContext,
	UUID.randomUUID().hashCode(),
	intent,
	PendingIntent.FLAG_ONE_SHOT //일회용 펜딩 인텐트
)

시작할 액티비티로 MainActivity로 설정하고 createdAt 데이터도 추가하고, 이를 pendingIntent의 인자로 넘긴다.

 

3. Notifiaction Channel 생성

Android Oreo(8.0, API 26) 이후 부터는 Notification을 모두 채널에 할당해주어야 한다. 이렇게 생성한 채널에는 채널에 오는 notification에 적용되는 시각/음향적 동작을 설정할 수 있다.

val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
    val channel = NotificationChannel(
        resources.getString(R.string.default_notification_channel_id), //채널 ID
        "CHATTING", //채널명
        IMPORTANCE_HIGH //알림음이 울리며 헤드업 알림 표시
    )
    channel.apply {
        enableLights(true)
        lightColor= Color.RED
        enableVibration(true)
        description = "notification"
        notificationManager.createNotificationChannel(channel)
    }
}

 

4. Notifiaction 빌드

NotificationBuilder를 반환하는 함수를 작성하고, 아까 만든 Builder에는 pendingIntent를 설정해준다.

private fun getNotificationBuilder(title: String, content: String, pendingIntent: PendingIntent) : NotificationCompat.Builder {
    return NotificationCompat.Builder(this, resources.getString(R.string.default_notification_channel_id))
        .setContentTitle(title)
        .setContentText(content)
        .setContentIntent(pendingIntent)
        .setGroupSummary(true)
        .setAutoCancel(true)
        .setSmallIcon(R.drawable.logo)
        .setShowWhen(true)
}

 

5. Notification 알림

최종적으로 NotificationBuilder를 통해 빌드하여 Notification을 생성하고 발생시킨다.

val notification = getNotificationBuilder(title,content,pendingIntent).build()
notificationManager.notify(NOTIFICATION_ID,notification)

 

@ Activity에서 onNewIntent 오버라이딩

이후 사용자가 생성된 notification을 클릭하면 MainActivity가 시작되고 activity에서는 다음과 같이 onNewIntent를 오버라이딩하여 pendingIntent로 전송한 intent의 데이터를 받는다.

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    val createdAt = intent.extras!!.getString("createdAt")
}
# onNewIntent란?
안드로이드에서는 생명주기 중 onCreate에서 보통 인텐트를 받아 처리하곤 한다. 하지만, 앱이 실행 중인 동안에 새로운 intent가 도착한다면 이를 onCreate에서 처리하고 싶지만 onCreate는 액티비티가 생성될 때 단 한번만 호출된다. 그렇기에 액티비티가 새로 생성되지 않고 사용 중인 상황에서 intent를 받기 위한 메서드가 onNewIntent 메서드이다.

 

 

4. 앱이 완전히 종료된 상황(Background)에서 notification 클릭 시 데이터 전달

앱이 완전히 종료된 상황에서 전달되는 FCM의 notification은 우리가 onMessageReceived에서 생성한 notification이 아니다. onMessageReceived는 앱이 동작 중인 상황에만 호출됨에 유의해야 한다. 이 때 시스템 자체적으로 생성된 notification에는 intent로 서버에서 전송한 데이터를 저장하고 있다. 이 결과로 실행되는 activity에서 해당 intent를 받아 처리할 수 있다. 이를 위해 1번과 2번 과정에서 action_click을 설정한 것이다.

 

onCreate(...){
    val data = getIntent().getExtras();
}

따라서 위 처럼 foreground에서 처리하는 방법에 비해 매우 간단하다.

 

 

참고. Android 앱에서 메시지 수신  |  Firebase (google.com)

만약, 포그라운드와 백그라운드 모두에서 onMessageRecevied를 통해 일관된 작업을 하고 싶다면 1. 서버에서 설정 과정에서 아래처럼 notification을 지워야 한다.

const message = {
       data: {
	   createdAt: chat.createdAt,
	   message:"hello",
	   click_action: "NOTIFICATION_CLICK",
      },
      tokens: token
   };

추가로 Android O 이후 백그라운드 실행 제한에 의해 onMessageReceived가 제공한 메시지는 수신한 지 20초(Marchmallow 버전의 경우 10초) 이내에 처리되어야 한다.