IT_Programming/Android_Java

[펌] Android FCM Data와 Notification / [Android] FCM Background Push 이슈 대응 방법

JJun ™ 2019. 1. 31. 18:18



 * 출처

 : https://medium.com/harrythegreat/android-fcm-data와-notification-36a5285cfae5

 : https://layers7.tistory.com/46





Android FCM Data와 Notification

Data Message와 Notificiation Message의 차이



들어가기에 앞서

처음 Document를 읽고 테스트를 해보았을때는 잘 동작을 했습니다 하지만 Data를 처리해야할 일이 생길때부터 혼돈이 시작됩니다. 데이터를 보낼때 제가 예상했던것과 다르게 Notication이 동작하고 Document를 보면 볼수록 더 큰 혼돈에 빠지게 됩니다. 게다가 Doze 모드나 절전상태에서는 더 혼란스럽게 동작합니다.

Notification Message

Notification만 보내는 경우

{
"message":{
"notification":{
"body" : "This week's edition is now available.",
"title" : "NewsMagazine.com",
}
}

Notification Message는 위와같이 Notification만 보내는 메세지입니다. 또한 우리가 Firebase Console에서 Cloud Message를 통해 보내는 경우에 해당합니다. 이 경우 메세지는 높은 우선순위를 가지며 디바이스가 잠자기 상태나 절전상태에 있더라도 Notification에 메세지가 표시됩니다.

앱이 백그라운드 상태에 있다면 시스템 트레이를 통하여 Notificiation이 표시가되고 포어그라운드 상태에 있다면 onMessageReceived를 통해 데이터가 처리됩니다.



 override fun onMessageReceived(remoteMessage: RemoteMessage) {

  // 앱이 포어그라운드 상태에서 Notificiation을 받는 경우 
  if (remoteMessage.notification != null) {
      // 해당 데이터를 처리
      updateContent(remoteMessage.notification)
  }
  private fun updateContent(notification: RemoteMessage.Notification) {
      // 업데이트 시켜줄 내용
  }
}


Notification과 data를 함께 보내는 경우

{
"message":{
"notification":{
"body" : "This week's edition is now available.",
"title" : "NewsMagazine.com",
}
"data" : {
"Nick" : "Mario",
"Room" : "PortugalVSDenmark"
}
}

Data와 Notifcation Message를 함께보낼때는 Data만 보냈을때와는 다르게 동작합니다. 포어그라운드 상태때는 동일하게 onMessageReceived를 처리가 되지만 백그라운드일때는 조금 다릅니다. 우선 Notification이 시스템 트레이를 통해 기기에서 표시가되고 onMessageReceived를 통해 data가 전달되지 않습니다.

사용자가 표시된 Notification을 탭한경우 앱이 실행되며 액티비티에서 getIntent를 통해서만 데이터를 수신받습니다. 하지만 사용자가 Notification을 지우거나 탭하지 않는다면 이때 함께 전달된 data는 앱에서 전달받을 수 없습니다.

Data Message

{
"data" : {
"Nick" : "Mario",
"Room" : "PortugalVSDenmark"
}
}

데이터 메세지는 클라이언트 앱에서 데이터를 처리해야하거나 혹은 커스텀된 알림을 보여줄때 사용합니다. 데이터 메세지는 Normal Priority를 기본으로 가지며 모바일 기기가 Doze상태 혹은 절전상태에 있을때 처리를 미루게됩니다.

onMessageReceived()을 통해 데이터를 처리해야하며 이 경우 짧은 시간의 처리기간을 가지게됩니다. 또한 4KB 이상의 Payload 제한을 가지기때문에 이 경우 Workmanger나 Job Schduler를 쓰는것이 바람직합니다.

Data Message의 Workflow

  • ● FCM을 통해 메세지를 전송합니다.
  • ● onMessageReceived에서 사용자에게 Nofication을 먼저 보여줍니다.
  • ● 수신받은 데이터를 처리합니다.
  • ● 사용자에게 먼저 보여준 Nofication에서 변동사항이 생긴다면 Notifcation을 업데이트합니다.

위와같은 Workflow를 구성하는것은 메세지를 처리하는도중 inactive가 될 수 있기때문에 변동사항에 대해 Notification해야하는 내용이 있다면 수신받은 즉시 메세지를 보여준 후 데이터를 처리한 후에 조금 전 보여준 메세지를 업데이트하는것이 바람직합니다.

만약 데이터의 처리가 끝나지 않았는데 앞서 보여준 메세지를 사용자가 탭하여 앱을 실행한경우 클라이언트에서 스케쥴링을 체크한 후 업데이트하여줍니다.

 

override fun onMessageReceived(remoteMessage: RemoteMessage) { // 데이터 메세지를 분기하여줍니다. if (remoteMessage.data.isNotEmpty()) { // 사용자에게 먼저 메세지를 보여줍니다. sendNotification(notificationMessage) // 수신받은 데이터를 처리합니다. updateContent(remoteMessage.data) /* 수신받은 데이터가 크거나 시간이 20초 이상 소요된다면 WorkManager 혹은 Job Schduler를 사용합니다. */ if (remoteMessage.data["url"] != null) { scheduleJob() } } 

} 

 

때때로 알림이 오지 않다가 핸드폰의 잠금을 풀었을때 오는 메세지들을 볼 수 있습니다. 이 경우 모바일에서 절전상태 혹은 Doze상태에서 Data Message의 처리를 미루었다가 잠금을 풀었을때 Data Message를 처리하는것입니다. 때문에 Data Message를 보낼때는 꼭 사용자 디바이스에서 Doze상태에 대해 고려해야합니다.(Doze 상태에대한 자세한 내용은 도큐먼트에서 확인할 수 있습니다.) Alarmy앱의 경우 갤럭시에서 설치시 사용자에게 절전모드 제외앱으로 설정하도록 안내 Dialog를 표시하고있습니다.

마치며

기존 GCM 레퍼런스는 많은데 반하여 FCM은 상대적으로 적고 또한 Data와 Message에 대해 상세하게 다룬 데이터를 찾기 힘들어 많은 삽질을 하였습니다. 저와 비슷한 난관에 봉착하신 분들이 이 글을 읽고 더 빠르게 문제들을 해결했으면 좋겠습니다.






이번 포스팅에서는 Firebase를 이용한 Push 처리 시 발생되는 Background 이슈에 대해 기술한다.



Foreground? Background?


Firebase를 이용한 Push처리시에 대부분이 콘솔 혹은 포스트맨을 통해 간략한 테스트 후 앱에서 정상수신이 되는 것을 보고 마무리 하는 경우가 많다. 만약 여기까지 성공했다면, 적어도 Foreground에선 정상적으로 처리되고 있다고 생각하면 된다.


문제는 Background에서 발생된다.


일반적으로 FCM은 SDK 내 에서 이벤트를 받아서 FirebaseMessagingService를 통해 사용자에게 제공되는 형태이다.


여기서 작은 이슈가 발생하는데, 앱이 Background 상태에 있는 경우(앱 자체가 시작되지 않았거나, 앱이 메모리에서 내려간 경우) SDK에서 전달받은 데이터에 키를 캐치하여 'Notification'키가 있는 경우, SDK 내 에서 처리하고 우리가 흔히 아는 onMessageReceived로 제공하지 않는다.


다시 말해, Push를 통해 어떤 데이터를 수신하여 그 데이터를 이용하여 어떤 액션이 필요한 경우, Background에서 수신된 Push 정보를 클릭 시 개발자가 의도한 액션이 처리되지 않을 수 있다.


정리하면, 사용자는 앱이 Background 상태에 있을 때는 앱을 실행시키는 액션 이외에 푸시에서 넘어오는 데이터 처리가 사실상 불가하다고 보면 된다.



그럼 어떻게 해야할까?

위의 이슈를 처리하기 위해 여러분은 작은 작업을 해줘야한다.

작은 작업이란 서버 또는 콘솔에서 전달되는 데이터에 'Notification'키로 설정되는 데이터가 없거나, 'Notification'키 내에 Intent 필터를 걸어줄 수 있는 액션을 함께 내려주면, 사용자는 Push를 통해 전달되는 데이터 처리가 가능하다.


Notification 키가 포함되어 내려오는 경우

Notification 키가 있는 푸시 데이터
{
  "registration_ids" : ["단말 토큰 키"],
    "notification" : { // 해당 키에 처리할 데이터를 넘겨준다.
      "title" "타이틀"
      "body" "바디"
    },
    "data" : {
        "timestamp":1539936478695
    }
}


Notification 키가 없이 내려오는 경우

Notification 키가 없는 푸시 데이터
{
  // Notification 필드가 없으면 data를 정상적으로 넘겨준다.
  "registration_ids" : ["단말 토큰 키"],
  "data" : {       
        "title" "타이틀"
        "body" "바디"
        "timestamp":1539936478695
    }
}



Intent 처리 방법


Intent 처리 방법의 경우, Notification 내 click_action필드를 함께 주어서, Push 클릭 시 진입되는 Activity에서 데이터를
꺼내 쓰는 방법이다.


Intent 필터 처리 방법
{
  "registration_ids" : ["단말 토큰 키"],
    "notification" : {
      "title" "타이틀"
      "body" "바디"
      "click_action" ".MainActivity"
    },
    "data" : {
        "timestamp":1539936478695
    }
}


<activity android:name=".MainActivity">
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>



public class MainActivity {

@Override
protected void init() {
initView();
}

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get notification data info
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
//bundle must contain all info sent in "data" field of the notification
}
}

private void initView() {
}
}



이번 포스팅에서는 Push 처리 시 Background 상태에서 발생되는 이슈와 그 대응방법에 대해 기술하였다.
본 블로그를 방문하는 모든 분들이 많은 도움이 되었길 바라며 본 포스팅을 마친다.