【问题标题】:How can I receive a Push Notification when my Xamarin Android app is stopped?当我的 Xamarin Android 应用程序停止时,如何接收推送通知?
【发布时间】:2020-04-22 05:43:29
【问题描述】:

我见过 许多 不同类型的解决方案,这些解决方案在过去可能有效,但对我自己没有任何可靠的解决方案。这是一个人们说什么有效、什么无效、什么发生了变化等的雷区。但我不仅试图找到解决方案,还希望找到一种理解——因为现在我很困惑。


我现在能做什么 - 我的 Xamarin Forms 应用程序 (Android) 可以在应用程序位于前台/后台时接收推送通知,我也可以在用户点击这些通知时拦截它们,以便我可以告诉我的应用该做什么。

我正在尝试做的事情 - 基本上是上述情况,但处于应用已完全停止的状态。


我的 Firebase 消息传递设置已连接到 Azure 通知中心 - 不幸的是,我不会离开 Azure(以防万一有人建议放弃它)。我目前拥有的大部分信息是我设法从各种 Microsoft 文档(here 我不使用 AppCenter - 只是用它来交叉引用任何有用的代码、hereherehere)、其他 StackOverflow 问题(例如 hereherehere - 链接太多了)和 Xamarin 论坛 - 再次,如果使用了任何过时的代码,我们深表歉意(请让我知道 - 我已尽力使用最新的方法等)。

我发送的推送通知类型是 数据消息 which I read up on here,我在通知中使用自定义数据,因此我理解这是我想要的正确推送类型发送,如下图。

{
    "data": {
        "title": "Title Test",
        "body": "Push notification body test",
        "area": "SelectedPage"
    }
}

以下是迄今为止我在项目中设置的用于处理推送通知的当前代码。

清单

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.pushtesting" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
    <application android:label="Push Testing">

        <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>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

MainActivity.cs

我有LaunchMode = LaunchMode.SingleTop,我的理解是否正确,因为这是为了确保仍然使用当前活动而不是创建一个新活动 - 例如,当用户点击通知时 - 实施它加上额外的代码(下面)似乎表明这是真的。

protected override void OnNewIntent(Intent intent) {
    base.OnNewIntent(intent);

    String area = String.Empty;
    String extraInfo = String.Empty;

    if (intent.Extras != null) {
        foreach (String key in intent.Extras.KeySet()) {
            String value = intent.Extras.GetString(key);
            if (key == "Area" && !String.IsNullOrEmpty(value)) {
                area = value;
            } else if (key == "ExtraInfo" && !String.IsNullOrEmpty(value)) {
                extraInfo = value;
            }
        }
    }
    NavigationExtension.HandlePushNotificationNavigation(area, extraInfo);
}

当用户与之交互时,使用OnNewIntent拦截推送通知。

MyFirebaseMessaging.cs

using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Support.V4.App;
using Android.Util;
using Firebase.Messaging;
using PushTesting.Models;
using WindowsAzure.Messaging;

namespace PushTesting.Droid.Services {

    [Service]
    [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
    public class OcsFirebaseMessaging : FirebaseMessagingService {

        private const String NotificationChannelId = "1152";
        private const String NotificationChannelName = "Push Notifications";
        private const String NotificationChannelDescription = "Receive notifications";
        private NotificationManager notificationManager;

        public override void OnNewToken(String token) => SendTokenToAzure(token);

        /// <summary>
        /// Sends the token to Azure for registration against the device
        /// </summary>
        private void SendTokenToAzure(String token) {
            try {
                NotificationHub hub = new NotificationHub(Constants.AzureConstants.NotificationHub, Constants.AzureConstants.ListenConnectionString, Android.App.Application.Context);

                Task.Run(() => hub.Register(token, new String[] { }));
            } catch (Exception ex) {
                Log.Error("ERROR", $"Error registering device: {ex.Message}");
            }
        }

        /// <summary>
        /// When the app receives a notification, this method is called
        /// </summary>
        public override void OnMessageReceived(RemoteMessage remoteMessage) {
            Boolean hasTitle = remoteMessage.Data.TryGetValue("title", out String title);
            Boolean hasBody = remoteMessage.Data.TryGetValue("body", out String body);
            Boolean hasArea = remoteMessage.Data.TryGetValue("area", out String area);
            Boolean hasExtraInfo = remoteMessage.Data.TryGetValue("extraInfo", out String extraInfo);

            PushNotificationModel push = new PushNotificationModel {
                Title = hasTitle ? title : String.Empty,
                Body = hasBody ? body : String.Empty,
                Area = hasArea ? area : String.Empty,
                ExtraInfo = hasExtraInfo ? extraInfo : String.Empty
            };

            SendNotification(push);
        }

        /// <summary>
        /// Handles the notification to ensure the Notification manager is updated to alert the user
        /// </summary>
        private void SendNotification(PushNotificationModel push) {
            // Create relevant non-repeatable Id to allow multiple notifications to be displayed in the Notification Manager
            Int32 notificationId = Int32.Parse(DateTime.Now.ToString("MMddHHmmsss"));

            Intent intent = new Intent(this, typeof(MainActivity));
            intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
            intent.PutExtra("Area", push.Area);
            intent.PutExtra("ExtraInfo", push.ExtraInfo);

            PendingIntent pendingIntent = PendingIntent.GetActivity(this, notificationId, intent, PendingIntentFlags.UpdateCurrent);
            notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);

            // Creates Notification Channel for Android devices running Oreo (8.0.0) or later
            if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O) {
                NotificationChannel notificationChannel = new NotificationChannel(NotificationChannelId, NotificationChannelName, NotificationImportance.High) {
                    Description = NotificationChannelDescription
                };

                notificationManager.CreateNotificationChannel(notificationChannel);
            }

            // Builds notification for Notification Manager
            Notification notification = new NotificationCompat.Builder(this, NotificationChannelId)
            .SetSmallIcon(Resource.Drawable.ic_launcher)
            .SetContentTitle(push.Title)
            .SetContentText(push.Body)
            .SetContentIntent(pendingIntent)
            .SetAutoCancel(true)
            .SetShowWhen(false)
            .Build();

            notificationManager.Notify(notificationId, notification);
        }
    }
}

最后,我使用 OnNewToken() 注册到 Azure 的 Firebase 类,OnMessageReceived() 被覆盖,因此我可以处理接收到的推送通知,然后是用于构建和显示通知的 SendNotification()

我们将不胜感激任何和所有的帮助!

Added Sample Project to GitHub

【问题讨论】:

  • 在 GitHub 上找到了一个针对这个确切问题的活动案例 - 尚无解决方案。虽然我仍然对我的示例项目的工作方式感到恼火。 github.com/xamarin/GooglePlayServicesComponents/issues/273
  • @AlexanderAlekseev,请在此处找到我的示例:github.com/MattVon/Xamarin-Push-Notification-Sample
  • @MattVon,谢谢。我将您的测试项目与我的项目进行了比较,发现了一些小的差异,但仍然没有运气:(
  • 是的,我认为几乎可以肯定是 Xamarin Forms 升级导致了问题 - 早期版本早于 4. 并且有效。我刚刚发现了一些东西(实际上是一些相关的东西):如果我在 Android 设置中启用开发人员选项,将默认调试应用程序设置为我的应用程序,然后指定它应该“等待调试器”,然后当我发送测试通知时该应用程序尝试启动。我收到“等待调试器”消息。不过,Visual Studio 不允许连接到该会话。但这确实意味着该应用程序正在尝试启动。注意:在调试模式下运行并关闭“强制停止”
  • 清单中似乎不再需要接收器的任何内容。我认为这是遗留配置。 FirebaseMessaging 类上的 Service 和 IntentFilter 属性就足够了(Xamarin 工具在 android 清单中注入了正确的东西)。不过还是没有运气。

标签: c# android firebase xamarin.forms firebase-cloud-messaging


【解决方案1】:

我遇到了很多问题(更多详情here):

  • 不熟悉 Android 应用架构以及它与 FirebaseMessagingService 生命周期的关系:有应用、服务和接收器。当“应用程序”(从用户的角度)关闭时,通过从最近刷卡,服务/接收器可以对“意图”(传入的通知消息)做出反应。当应用“强制停止”时,不会运行任何 UI、服务或接收器。
  • Visual Studio 强制在调试会话结束后停止应用程序。要诊断“停止”的应用,您需要在设备上运行该应用并查看设备日志。
  • FirebaseMessagingService 生命周期是这样的,当应用程序处于停止状态时,ctor 不再有权访问共享应用程序的属性或方法(我必须通过删除抽象并使代码平台特定于来解决这个问题 - 特别是DependencyService 无法使用,因为它不可用)。
  • MS 文档已过期。例如,Firebase 清单条目不再需要 Receiver 元素。
  • 您无法将调试器附加到 Visual Studio 中“停止”的应用程序。
  • 设备上的错误消息是神秘的。

最后解决方案是查看->其他 Windows->设备日志并再次运行应用程序以避免强制停止状态,以发现 ctor 生命周期问题,我不得不通过移出代码来解决这个问题这触及了 FirebaseMessagingService 中的共享应用程序库。

【讨论】:

  • 抱歉,我正在处理其他缺陷。提供了非常好的信息,我在调试时已经知道由于 VS 而停止的状态 - 讨厌的伎俩。希望我可以在我自己的项目中重新考虑这一点,看看我能不能走得更远。您是否有任何成功/更多信息可以帮助您到达任何地方?
  • 从字面上开始调试这个并采取检查日志的方法,结果我也得到了FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: You MUST call Xamarin.Forms.Init(); prior to using it.。一旦我删除了导致此问题的特定代码,它就会再次工作。
  • @MattVon 非常棒 :-) 这是一场史诗般的黑客会议,让一切都搞砸了。我认为如果 VS 允许调试器在滑动关闭状态下保持与应用程序的连接,我们本可以避免这一切。无论如何,很高兴我能帮上忙。
猜你喜欢
  • 2018-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-17
相关资源
最近更新 更多