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

FCM messages unable to be handled when app is killed

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

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

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

broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg= (has extras) }

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

这个 Intent 广播是因为隐式接收器更改而被取消,还是我做错了什么?

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

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

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

<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>

我的主要 activity 包括用于启动的意图过滤器 activity:

      <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>

我请求以下权限:

  <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" />

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

<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/..." />

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

编辑 1:

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

logcat upon receiving FCM remote message while app is killed

*******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);
            }
        }

        ...

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

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

按照步骤 here 进行操作,特别是设置您的 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" />

在您的 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>

可选择定义通知通道、通知图标(必须仅是白色,允许透明)和通知托盘展开时通知图标颜色的默认值,也在 <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/..." />

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

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

如果您希望确保在设备重启之后和设备解锁之前能够收到通知,您可以考虑让您的应用程序和 FirebaseMessagingService 实现 Direct Boot Aware(更多 here)

在您的 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); 
    }
}

将 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.** { *; } 

希望这对您有所帮助,如果我遗漏了什么,我会更新更多细节。

@AndrewH 解决方案对我有用。缺少一个细节。当应用程序被终止时,Firebase messaging service 也会被调用。当应用程序被终止时,只有服务的构造函数会被调用,因为在内部 firebase 知道你的应用程序被终止了。因此,在初始化任何将处理前台通知的代码或任何与 Xamarin forms 交互的代码之前,您应该检查 Xamarin forms 是否已初始化。例如:

if (Xamarin.Forms.Forms.IsInitialized)
            {
                // Do stuffs.
            }

否则您的应用会在收到来自终止状态的推送通知时崩溃。

您还应该知道,如果应用程序被终止或 Xamarin.Forms.Forms.IsInitialized == false,您不应该尝试执行任何代码。就别管了。 Firebase 只会为您显示通知。当用户在您的 MainActivity.OnCreate().

中单击系统托盘中的通知时,您将只处理通知