Kotlin FirebaseMessaging 通知受信時にアプリの状態を知る
通知を受信した際に、アプリがフォアグランド/バックグラウンド/ターミネートの状態で処理を分けたい場合がります。とくにフルスクリーン通知やFlutterでの処理を行う場合など。FirebaseMessagingService内でその状態を知ることができます。
フォアグランド/バックグラウンドか判定
PushNotificationListenerService.kt
class PushNotificationListenerService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val isForeground = isAppInForeground(this)
if (isForeground) {
Log.d("FCM", "アプリはフォアグラウンド")
handleForegroundNotification(message)
} else {
Log.d("FCM", "アプリはバックグラウンド")
handleBackgroundNotification(message)
}
}
private fun isAppInForeground(context: Context): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val appProcesses = activityManager.runningAppProcesses ?: return false
for (appProcess in appProcesses) {
if (appProcess.processName == context.packageName) {
return appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
}
}
return false
}
private fun handleForegroundNotification(message: RemoteMessage) {
// フォアグラウンド時の通知処理
Log.d("FCM", "フォアグラウンド通知処理")
}
private fun handleBackgroundNotification(message: RemoteMessage) {
// バックグラウンド時の通知処理
Log.d("FCM", "バックグラウンド通知処理")
}
}
こんなふうにして利用
AndroidManifest.xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- MESSAGING_EVENTコールバックハンドラ登録 -->
<service
android:name=".PushNotificationListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
フォアグランド/バックグラウンド/ターミネートの判定
PushNotificationListenerService.kt
class PushNotificationListenerService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val appState = getAppState()
Log.d("FCM", "アプリの状態: $appState")
when (appState) {
AppState.FOREGROUND -> {
Log.d("FCM", "フォアグラウンド: アプリが表示中です")
handleForegroundNotification(message)
}
AppState.BACKGROUND -> {
Log.d("FCM", "バックグラウンド: アプリは非表示ですが動作中です")
handleBackgroundNotification(message)
}
AppState.TERMINATED -> {
Log.d("FCM", "ターミネート: アプリが完全に終了していました")
handleTerminatedNotification(message)
}
}
}
private fun handleForegroundNotification(message: RemoteMessage) {
Log.d("FCM", "フォアグラウンド通知処理を実行")
}
private fun handleBackgroundNotification(message: RemoteMessage) {
Log.d("FCM", "バックグラウンド通知処理を実行")
}
private fun handleTerminatedNotification(message: RemoteMessage) {
Log.d("FCM", "ターミネート通知処理を実行")
// フルスクリーン通知などを呼び出し
}
private fun getAppState(): AppState {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val appProcesses = activityManager.runningAppProcesses
// アプリプロセスが存在しない場合はターミネート
if (appProcesses.isNullOrEmpty()) {
return AppState.TERMINATED
}
// アプリプロセスを検索
for (appProcess in appProcesses) {
if (appProcess.processName == packageName) {
return when (appProcess.importance) {
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND -> AppState.FOREGROUND
ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED -> AppState.BACKGROUND
else -> AppState.TERMINATED
}
}
}
// プロセス情報が取得できない場合もターミネートと見なす
return AppState.TERMINATED
}
enum class AppState {
FOREGROUND,
BACKGROUND,
TERMINATED
}
}
フルスクリーン通知の例 Android 14以降
// 新しいトークンが生成された時の処理
// 中でサーバにトークンを送信する処理などを定義
override fun onNewToken(p0: String) {
super.onNewToken(p0)
// チャンネルidを設定
addChannelId()
}
// チャンネルの設定
private fun addChannelId() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = resources.getString(R.string.channel_id)
val channelName = resources.getString(R.string.channel_name)
val importance = NotificationManager.IMPORTANCE_HIGH
val notificationChannel = NotificationChannel(channelId, channelName, importance).apply {
enableLights(true) // ライトの設定を有効にする
lightColor = Color.BLUE // ライトの色を設定
enableVibration(true) // バイブレーションを有効にする
description = "This channel is used for important notifications"
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
}
}
private fun sendNotificationFullScreen(title: String, text: String) {
// 通知タップ時に遷移するアクティビティを指定
val intent = Intent(this, MainActivity::class.java)
//val intent = Intent(this, CustomActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
// 何度も遷移しないようにする(1度だけ!)
val pendingIntent: PendingIntent = PendingIntent.getActivity(this,0, intent, PendingIntent.FLAG_IMMUTABLE)
// 通知メッセージのスタイルを設定(改行表示に対応)
val inboxStyle = NotificationCompat.InboxStyle()
val messageArray: List<String> = text.split("\n")
for (msg: String in messageArray) {
inboxStyle.addLine(msg)
}
// 通知音の設定
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
// 通知の見た目を設定
val notificationBuilder
= NotificationCompat.Builder(this, resources.getString(R.string.channel_id))
.setContentTitle(title)
.setContentText(text)
// ステータスバーに表示されるアイコン
.setSmallIcon(R.mipmap.ic_launcher)
// 上で設定したpendingIntentを設定
.setContentIntent(pendingIntent)
// 優先度を最大化
.setPriority(PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
// 通知音を出すように設定
.setSound(defaultSoundUri)
.setFullScreenIntent(pendingIntent, true)
// Android 8.0 以上はチャンネル設定 必須
// strings.xml に channel_id を指定してください
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationBuilder.setChannelId(resources.getString(R.string.channel_id))
}
// 通知を実施
// UUIDを付与することで各通知をユニーク化
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val uuid = UUID.randomUUID().hashCode()
notificationManager.notify(uuid, notificationBuilder.build())
}
補足
実行環境によってはうまくいかないこともあるようなので、、、
private fun getAppState(): AppState {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val appProcesses = activityManager.runningAppProcesses ?: return AppState.TERMINATED
// プロセスがリスト内に存在するかを確認
val appProcess = appProcesses.find { it.processName == packageName }
if (appProcess == null) {
// プロセスが見つからない場合はターミネートとみなす
return AppState.TERMINATED
}
// プロセスが見つかった場合は状態を判定
return when (appProcess.importance) {
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND -> AppState.FOREGROUND
ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED -> AppState.BACKGROUND
else -> AppState.TERMINATED
}
}
Comments
Let's comment your feelings that are more than good