应用程序被终止时无法处理 FCM 消息

And*_*w H 2 android firebase xamarin.forms firebase-cloud-messaging

我一直在阅读各种教程、其他 SO 线程以及官方 Android 开发人员和 Firebase 文档,但无济于事。我已经尝试了几乎所有的方法,但我已经耗尽了精力和时间,因为我正在修复以前可以工作但不再工作的通知系统。

我正在使用 Azure 通知中心将通知分发到 FCM 以及其他推送通知平台。我的 FCM 项目仅针对 Android。我的应用程序是使用所有依赖项的最新 NuGet 包版本(Xamarin.Forms 5.xxx、Firebase.Messaging 122.0 等)在 Xamarin.Forms 中构建的。

目前,应用程序运行时接收的远程消息或在后台通过继承和实现 FirebaseMessagingService 的自定义服务完美地工作。一旦应用程序被终止(任务切换器 -> 滑动应用程序),在发送更多消息后,我开始看到包含以下内容的 Logcat 消息:

广播意图回调: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg= (有额外内容) }

我知道 Google 改变了 API 26 及更高版本中隐式接收器的工作方式,并且我的理解是此操作 ( com.google.android.c2dm.intent.RECEIVE) 不包含在例外列表中,因此我不知道如何在后台侦听和处理消息。我最近在 2019 年 7 月在其他线程中读到,在应用程序被终止时收到的 FCM 消息应该直接发送到通知托盘。这不会是一个普遍存在的问题,因为许多应用程序在被终止时都会发送通知,因此我希望获得一些当前信息来指导我找到解决方案。

这个意图广播是否由于隐式接收器更改而被取消,或者我做错了什么?

我正在运行 Android 10 的 OnePlus 7 Pro 上进行测试,所以我想知道这是否是其他人在华为和小米等 OEM 设备上提到的电池优化问题。

我的应用程序的目标 Android API 级别 29,最低 API 21

我为我的主要活动和接收器启用了直接启动感知,以确保接收器在启动时和用户打开应用程序之前拦截我的应用程序的意图:

<receiver android:directBootAware="true" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND" android:name=".NotificationReceiver">
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <category android:name="<package>" />
    </intent-filter>
</receiver>
Run Code Online (Sandbox Code Playgroud)

我的主要活动包括作为启动活动的意图过滤器:

      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <intent-filter>
        <action android:name=".MainActivity" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
Run Code Online (Sandbox Code Playgroud)

我请求以下权限:

  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
  <uses-permission android:name="android.permission.WAKE_LOCK" />
  <uses-permission android:name="android.permission.GET_ACCOUNTS" />
Run Code Online (Sandbox Code Playgroud)

meta-data我在清单标签中定义了以下标签<application>

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/..."/>
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/..." />
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/..." />
Run Code Online (Sandbox Code Playgroud)

这些值非常适合在后台收到的通知,因此我知道它们配置正确。

编辑1:

自从发布以来,我做了更多的挖掘和调试,我确实看到了这一点,如果我的自定义 BroadcastReceiver 也没有监听我的应用程序的 c2dm.intent.RECEIVE 操作:

  • 实际上是通过Google提供的内部FirebaseInstanceIdReceiver获取我在应用程序被终止时发送的远程消息,该内部FirebaseInstanceIdReceiver是记录的FCM设置的一部分(我删除了前面提到的NotificationReceiver服务)
  • 正在开始我的 FirebaseMessagingService 的自定义实现(请参见下面的屏幕截图)
  • 没有触发我的 FirebaseMessagingService 中的 OnMessageReceived 过载(请参见下面的屏幕截图)

当应用程序被终止时收到 FCM 远程消息时的 logcat

*****FirebaseService 部分代码:

    [Service(DirectBootAware = true, Exported = true, Enabled = true)]
    [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
    [IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
    public class MyFirebaseService : FirebaseMessagingService
    {
        public MyFirebaseService()
        {
            
        }

        public override ComponentName StartService(Intent service)
        {
            Log.Info("GCM", $"MyFirebaseService started from intent {service}");

            return base.StartService(service);
        }

        public override void OnMessageReceived(RemoteMessage message)
        {
            var notification = message.GetNotification();
            Log.Info("GCM", $"Received remote message from FCM. Has notification: {notification != null}, has data: {message.Data != null}");

            if (notification != null)
            {
                message.Data.TryGetValue("route", out string route);
                SendNotification(notification.Title, notification.Body, route);
            }
            else
            {
                ParseDataNotification(message);
            }
        }

        ...
Run Code Online (Sandbox Code Playgroud)

And*_*w H 5

我能够解决这个问题。我的 FirebaseMessagingService 实现在构造函数中进行了依赖注入调用,当 FirebaseIidInstanceReceiver 在后台启动服务时,该调用失败。这导致服务无法启动,并且在应用程序被终止时不会生成 Android 通知。

由于我已经进行了大量的挖掘,并且有关该主题的信息如此分散且过时,因此我将尝试在此处编译我所知道的结果,形成一个可行的解决方案:

请按照此处的步骤操作,特别是设置您的 FCM 项目并下载google-services.json文件。

确保您的清单声明了以下权限:

<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
Run Code Online (Sandbox Code Playgroud)

在 AndroidManifest 标记中添加以下内容<application>以侦听消息接收:

<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
        <category android:name="${applicationId}" />
    </intent-filter>
</receiver>
Run Code Online (Sandbox Code Playgroud)

(可选)定义通知通道、通知图标(必须仅为白色,允许透明度)以及通知托盘展开时的通知图标颜色的默认值,也在清单标记内<application>

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/..."/>
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/..." />
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/..." />
Run Code Online (Sandbox Code Playgroud)

创建一个继承自 的自定义类FirebaseMessagingService。在 Xamarin.Forms 中,您将需要此类的Xamarin.Firebase.Messaging NuGet 包。在您的实现中,您应该覆盖OnMessageReceived(RemoteMessage)并添加应用程序逻辑,该逻辑将处理包含notification前台属性的消息以及仅包含data前台和后台属性的消息。您的类应使用以下属性进行修饰(请注意,DirectBootAware 是可选的;请参见下文):

[Service(DirectBootAware = true, Exported = true, Enabled = true)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
Run Code Online (Sandbox Code Playgroud)

如果您希望确保在设备重新启动后和设备解锁之前可以收到通知,您可以考虑使您的应用程序和 FirebaseMessagingService 实现Direct Boot Aware(更多信息请参见此处

在您的 MainActivity 中,确保为运行 Android O 或更高版本的设备创建通知通道,并在以下期间的某个时刻调用此方法OnCreate

private void CreateNotificationChannel()
{
    if (Build.VERSION.SdkInt < BuildVersionCodes.O)
    {
        // Notification channels are new in API 26 (and not a part of the
        // support library). There is no need to create a notification
        // channel on older versions of Android.
        return;
    }

    var channelId = GetString(Resource.String./*res id here*/);
    var notificationManager = (NotificationManager)GetSystemService(NotificationService);

    // Don't re-create the notification channel if we already created it
    if (notificationManager.GetNotificationChannel(channelId) == null)
    {
        var channel = new NotificationChannel(channelId,
            "<display name>",
            NotificationImportance.Default);

        notificationManager.CreateNotificationChannel(channel); 
    }
}
Run Code Online (Sandbox Code Playgroud)

将 ProGuard 配置文件(“proguard.cfg”)添加到您的 Android 项目中,以防止 SDK 链接器杀死 Google Play 和 Firebase 库。在 Visual Studio 中编辑此文件的属性并将“生成操作”设置为ProguardConfiguration. 即使下拉列表中缺少该选项,Xamarin 也会识别它。如果您在构建中使用 d8 和 r8 而不是 dx 和 ProGuard,Xamarin 仍将使用此配置文件并符合您在其中定义的规则。

# Keep commands are required to prevent the linker from killing dependencies not directly referenced in code
# See: https://forums.xamarin.com/discussion/95107/firebaseinstanceidreceiver-classnotfoundexception-when-receiving-notifications

-dontwarn com.google.android.gms.**
-keep class com.google.android.gms.** { *; }
-keep class com.google.firebase.** { *; } 
Run Code Online (Sandbox Code Playgroud)

希望这对您有所帮助,如果我错过了任何内容,我将更新更多详细信息。