Обрабатывать фоновые push-уведомления с помощью Firebase, поддерживая Doze - PullRequest
3 голосов
/ 28 июня 2019

У меня есть приложение Android, настроенное на получение push-уведомлений через Firebase, и у меня возникают проблемы с его работой, когда телефон находится в режиме ожидания.

Приложение правильно получает push-уведомления, независимо от того, находится ли оно вна переднем плане или в фоновом режиме.Для этого я использую только поле data в push-уведомлении, чтобы иметь возможность обрабатывать все, что приходит, независимо от состояния приложения.

Я реализовал свой сервис для получения уведомлений следующим образом:

class MyFirebaseMessagingService : FirebaseMessagingService() {

   override fun onMessageReceived(p0: RemoteMessage?) {
        Timber.d("Push notification received")
        super.onMessageReceived(p0)
        when (p0!!.data["ch"]) {
            NotificationType.VoIP.channelType() -> handleVoIPNotification(p0.data)
            NotificationType.Push.channelType() -> handlePushNotification(p0.data)
        }
    }

}

Свойство ch определяет тип уведомления и отправляется из моего бэкэнда: так как мое приложение имеет функцию видеовызова, когда кто-то вызывает бэкэнд, отправляет уведомление с ch = voip и устанавливает приоритет сообщения на high, как описано в руководстве по Firebase .

Функция handleVoIPNotification содержит следующее:

private fun handleVoIPNotification(data: Map<String, String>) {
        val gson = Gson()
        val jsonElement = gson.toJsonTree(data)

        try {
            val voIPNotification = gson.fromJson(jsonElement, VoIPNotification::class.java)

            Timber.i("VoIP Notification received: %s", voIPNotification.action.name)

            // pass the incoming call data to the Call Manager.
            CallManager.getInstance().handleNotification(voIPNotification)
        } catch (exc: Exception) {
            Timber.e(exc)
            Timber.i("Invalid VoIP notification received: %s", data)
        }
    }

Диспетчер вызововзатем обновляет свойство с именем currentCall и завершает работу:

this.currentCall.apply {
        token = notification.token
        roomName = notification.room
        username = notification.nickname

        status.value = Call.CallStatus.Ringing
    }

Свойство status является реализацией BehaviorSubject, наблюдаемой другим объектом, который реагирует на изменения статуса вызова:

currentCall.status.observable
                .distinctUntilChanged()
                .subscribe {
                    Timber.d("Call status changed: $it")

                    when (it) {
                        Call.CallStatus.Ringing -> {
                            this.showIncomingCallNotification()
                        }
                        Call.CallStatus.Declined, Call.CallStatus.Ended -> {
                            this.dismissIncomingCallNotification()
                            this.refreshIncomingCallActivity()
                        }
                        Call.CallStatus.Connecting -> {
                            this.dismissIncomingCallNotification()
                            this.presentOngoingCallActivity()
                        }
                        else -> { /* ignored */ }
                    }
                }.disposedBy(this.disposeBag)

showIncomingCallNotification имеет следующий вид:

fun showIncomingCallNotification() {
        val intent = Intent(Intent.ACTION_MAIN, null).apply {
            flags = Intent.FLAG_ACTIVITY_NO_USER_ACTION or Intent.FLAG_ACTIVITY_NEW_TASK
            setClass(configuration.context, configuration.incomingCallActivityType.java)
        }

        val pendingIntent = PendingIntent.getActivity(configuration.context, configuration.requestCode, intent, 0)

        val builder = NotificationCompat.Builder(configuration.context, configuration.notificationChannel)
                .setOngoing(true)
                .setContentIntent(pendingIntent)
                .setFullScreenIntent(pendingIntent, true)
                .setSmallIcon(configuration.notificationIcon)
                .setContentTitle(currentCall.username)
                .setContentText(configuration.context.getString(configuration.notificationText))

        val notification = builder.build()
        notification.flags = notification.flags or Notification.FLAG_INSISTENT

        configuration.notificationsManager.getSystemNotificationManager().notify(0, notification)
    }

Этот код показывает уведомление, которое при нажатии открывает IncomingCallActivity и позволяет пользователю принять oВы отклоняете вызов.Это уведомление также отвечает за звонок и вибрацию телефона.

Все это прекрасно работает, когда приложение открыто на переднем плане или в фоновом режиме, пока экран включен или он только что выключен.,Если я подожду некоторое время (от 5 минут до часов, это зависит), все перестает работать: моя служба Firebase не вызывается, когда push-уведомление отправляется с моего бэкэнда (и я вижу, что push-уведомление было отправлено правильно).При включении экрана уведомление о входящем вызове отображается правильно.

У меня есть журналы, которые ясно показывают, что функция Firebase onMessageReceived даже не вызывается, пока я не включу экран.

Я тысячу раз читал Android Developer - Optimize для Doze и ожидания приложений , и они четко заявляют, что FCM с высокоприоритетными сообщениями - это правильный подход для пробуждения приложения, когда телефон находится в режиме ожидания.

FCM оптимизирован для работы в режимах ожидания Doze и App Standby с помощью высокоприоритетных сообщений FCM.Высокоприоритетные сообщения FCM позволяют надежно разбудить ваше приложение для доступа к сети, даже если устройство пользователя находится в режиме ожидания или приложение находится в режиме ожидания приложения.В режиме ожидания или ожидания приложения система доставляет сообщение и дает приложению временный доступ к сетевым службам и частичным блокировкам пробуждения, а затем возвращает устройство или приложение в состояние ожидания.

В любом случае, это не работает.Я пробовал на разных телефонах, и я могу сказать, что поведение на Android 9 намного хуже, тогда как на Android 7 и ниже это не так часто.

Я видел и пробовал решения, предложенные в этом другом вопрос , но ни один из них, кажется, не работает, и, даже если в одном из ответов они говорят, что это происходит, только если пользователь украл приложение из многозадачности, я могу сказать, что это происходит со мнойдаже если приложение не было принудительно остановлено.

Есть еще один вопрос, более похожий на мою проблему, здесь , но все предложенные решения не работают.

Я не знаю, как решить эту проблему, и это очень плохая проблема, которая влияет на удобство использования моего приложения, так как пользователь не может отвечать на видеозвонки, когда он не использует свой телефон.Я также обнаружил, что другие люди онлайн сообщают об этой проблеме, но все эти разговоры в какой-то момент умирают без реального решения ...

Кто-нибудь может мне помочь?

...