앞으로 개발하게 될 프로젝트에서 반복적이지만 간단한 푸시 알림 서비스를 사용하게 될 것 같아
개발 전 미리 FCM을 이용한 푸시 알림에 대해 공부하려고 포스팅을 작성하게 되었다.
FCM
Firebase 클라우드 메시징(FCM)은 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션입니다.
- 알림 메시지 / 데이터 메시지 전송
- 다양한 메시지 타겟팅 : 단일 기기, 기기 그룹, 주제를 구독한 기기 등 3가지 방식으로 앱에 메시지를 배포할 수 있음
- 클라이언트 앱에서 메시지 전송 : FCM의 신뢰성 높고 배터리 효율적인 연결 채널을 사용하여 메시지를 보낼 수 있음
FCM vs. AlarmManager vs. WorkManager
FCM : 네트워크 기반의 푸시 알람 서비스
👍 네트워크만 연결되어 있다면 원하는 기능을 서버에서 구현한 뒤, 배포하면 되므로 많이 이용하고 있음
☝️ 오프라인 환경에서도 사용자에게 알람을 보내고 싶다면?...
AlarmManager : 시스템 알람 서비스
👍 앱 외부에서 작동하기 때문에 앱이 실행 중이 아닐 때도 이벤트를 트리거할 수 있음
☝️ Doze 모드에서는 알람이 울리지 않는다...
WorkManager : 백그라운드 작업 라이브러리
👍 Doze 모드, 배터리 세이버 등의 제안 사항 속에서 안전하게 백그라운드 작업을 할 수 있음
☝️ ...없다?
FCM이 메세지를 보내는 과정
- FCM 서버에 Token 요청 & 획득
- 앱 서버에 Token 저장 (서버가 FCM 서버에 메시지 전송을 요청할 때 어디로 보낼지 구분하는 용도)
- Token을 활용하여 메세지 전송 요청 (앱 서버 ----------- Token & 메세지 데이터 -----------> FCM 서버)
- 메세지 전송 (FCM 서버는 요청받은 메세지를 Token에 해당하는 디바이스에 전송)
- 메세지 수신
기존 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()에 전달해야 함
- 어떤 화면으로 이동할 것인지 Intent 생성
- PendingIntent를 위에서 생성한 Intent 인스턴스를 매개변수로 하여 생성
- 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 → 백 스택이 생성되어 뒤로 및 위로 버튼에 대한 사용자의 기대가 유지
*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 >
- https://developer.android.com/guide/topics/ui/notifiers/notifications?hl=ko
- https://developer.android.com/training/notify-user/build-notification?hl=ko
- https://firebase.google.com/docs/cloud-messaging/concept-options?hl=ko
- https://maejing.tistory.com/entry/Android-FCM%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-Push-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0
- https://onlyfor-me-blog.tistory.com/183
- https://tech.junhabaek.net/%EB%B0%B1%EC%97%94%EB%93%9C-%EC%84%9C%EB%B2%84-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-presentation-layer-3-%EC%9D%91%EB%8B%B5-%EC%9C%A0%ED%98%95%EC%97%90-%EB%94%B0%EB%A5%B8-variation-2-push-notification-1eacb4df4a7e
- https://youngest-programming.tistory.com/491
'ANDROID > Android 앱 프로그래밍' 카테고리의 다른 글
[Android] 위젯 속성 지정하는 헷갈리는 코드 (feat. Kotlin) (0) | 2022.03.04 |
---|---|
[Android] 백그라운드 작업 (0) | 2022.01.27 |
[Android] Context란? (0) | 2022.01.11 |
[Android] Application 클래스 (0) | 2022.01.10 |
[Android] 뷰 바인딩(View Binding) in Activity, Fragment, RecyclerView (0) | 2021.08.10 |
[Android] 데이터베이스(Database)와 내용 제공자(Content Provider) (0) | 2021.07.06 |
[Android] 모바일 데이터베이스(Database)와 테이블(Table) 생성 (3) | 2021.07.04 |
[Android] Retrofit2를 사용한 API 통신 (0) | 2021.07.04 |
댓글