android - 应用程序被杀死时无法处理 FCM 消息
问题描述
我一直在阅读各种教程、其他 SO 线程以及官方的 Android 开发人员和 Firebase 文档,但无济于事。我已经尝试了几乎所有方法,但我的精力和时间都快用完了,因为我正在修复以前工作但不再工作的通知系统。
我正在使用 Azure 通知中心将通知分发到其他推送通知平台中的 FCM。我的 FCM 项目仅针对 Android。我的应用是使用所有依赖项(Xamarin.Forms 5.xxx、Firebase.Messaging 122.0 等)的最新 NuGet 包版本在 Xamarin.Forms 中构建的。
目前,在应用程序运行或后台接收的远程消息通过继承和实现 FirebaseMessagingService 的自定义服务完美工作。一旦应用程序被终止(任务切换器 - > 将应用程序滑开),在发送更多消息后,我开始看到 Logcat 消息,其中包含以下内容:
广播意图回调: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 消息应该直接发送到通知托盘。这不会是一个普遍的问题,因为许多应用程序在被杀死时会发送通知,所以我希望获得一些当前信息来指导我找到解决方案。
是否由于隐式接收器更改而取消了此意图广播,还是我做错了什么?
我正在使用 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>
我的主要活动包括作为启动活动的意图过滤器:
<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" />
我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/..." />
这些值非常适合在后台接收到的通知,所以我知道它们配置正确。
编辑1:
自发布以来,我做了更多的挖掘和调试,我确实看到了,没有我的自定义 BroadcastReceiver 也监听我的应用程序的 c2dm.intent.RECEIVE 操作:
- 事实上,当应用程序通过谷歌提供的内部 FirebaseInstanceIdReceiver 被杀死时,我发送的远程消息是记录在案的 FCM 设置的一部分(我删除了前面提到的 NotificationReceiver 服务)
- 正在开始我的 FirebaseMessagingService 的自定义实现(见下面的截图)
- 没有在我的 FirebaseMessagingService 中触发 OnMessageReceived 重载(请参见下面的屏幕截图)
*******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 通知。
由于我已经进行了很多挖掘,并且有关此主题的信息非常分散且过时,因此我将尝试在此处编译我所知道的结果以形成一个可行的解决方案:
按照此处的步骤,特别是设置您的 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
. 在 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" })]
如果您希望确保在设备重新启动后和设备解锁之前可以接收到通知,您可以考虑让您的应用程序和 FirebaseMessagingService 实现直接启动感知(更多此处)
在您的 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.** { *; }
希望这会有所帮助,如果我错过了任何内容,我会更新更多详细信息。
推荐阅读
- ruby-on-rails - 如何在 f.collection_select 上提交事件,并根据下拉列表中选择的内容更改显示的内容?
- unity3d - 需要帮助 Assets\FlyLittleBird.cs(7,27): error CS1003: Syntax error, ',' expected
- javascript - 使用 date-fns 将日期解析为 UTC 的正确方法
- rxjs - 以编程方式离开 rxjs-for-await 循环
- postgresql - WAL 文件 LSN 如何反映它们在抽象 WAL 序列中的位置
- html - 包含iframe的flexbox内奇怪的CSS宽度?
- python - 使用带有 debugpy 的 VS Code 无法连接到 AWS EC2 实例
- wix - Wix 工具集 - CustomAction - ExeCommand - 1 失败 1 成功。你能帮助理解为什么吗?
- git - 检查我是否有对 ssh-git 存储库的密钥驱动访问而不克隆它
- typescript - 如何将运行时字符串转换为字符串枚举