【问题标题】:How to handle notifications with FCM when app is in either foreground or background当应用程序处于前台或后台时如何使用 FCM 处理通知
【发布时间】:2018-07-31 13:26:51
【问题描述】:

我使用 firebase 来构建我的项目。
它还将使用 FCM(firebase 云消息)。
但是有一个问题。
当应用程序处于后台时,我无法处理 FCM(创建我的自定义通知)。

The official site tutorial
案例 1:应用程序前台 -> 覆盖“onMessageReceived()”以创建您的自定义通知。
案例2:应用后台->系统会直接创建通知。我们不需要也不能做任何事情。因为在这种情况下它不会触发“onMessageReceived()”。

但是,如果我在应用程序处于后台时无能为力,则无法创建自定义通知。 (例如,用户点击通知后,会弹出一个窗口显示详细信息。)

那么当应用在后台时,我如何使用 FCM 处理通知?

【问题讨论】:

  • 您有什么问题,还是只是说您选择如何在自己的应用中处理消息?
  • @Doug Stevenson 我之前遇到过这个问题。然后我在追踪 Firebase 库代码后得到了答案。
  • 嗨莱昂。您实际上可以发布一个问题,例如帖子和您的实际答案。现在的情况可能会让一些社区成员感到困惑。
  • @LeonChang 检查这个,它会解决你的问题:stackoverflow.com/questions/48301350/…。如果它确实告诉我,我们可以将其标记为重复,谢谢!
  • @LeonChang 您可以在后台发送通知,通知的内容也将写入那里(意味着您无需单击它即可打开应用程序并查看内容),检查上面的链接以了解如何。

标签: android firebase notifications firebase-cloud-messaging


【解决方案1】:

有个坏消息。
Google 更改版本“com.google.firebase:firebase-messaging:11.6.0”中的 Firebase 源代码。
handelIntent 现在是“公共最终无效方法”。这意味着我们不能覆盖它。
如果要使用该解决方案,请将版本更改为“com.google.firebase:firebase-messaging:11.4.2”



试试我的方法。项目构建版本为Android 6.0以上(api level 23)可以完美运行,我已经试过了。

有比official site tutorial更好的方法

官网说app在后台时系统会创建通知。所以你不能通过覆盖“onMessageReceived()”来处理它。因为“onMessageReceived()”仅在应用程序处于前台时触发。

但事实并非如此。实际上通知(当应用在后台时)是由 Firebase 库创建的。

在我跟踪了 firebase 库代码之后。我找到了更好的方法。

第 1 步:在 FirebaseMessagingService 中覆盖“handleIntent()”而不是“onMessageReceived()”
为什么:
因为该方法将触发应用程序在前台或后台。所以我们可以在这两种情况下处理 FCM 消息并创建我们的自定义通知。

@Override
public void handleIntent(Intent intent) {
    Log.d( "FCM", "handleIntent ");
}


第 2 步。从 FCM 解析消息
如何:
如果您不知道您设置的消息格式。打印出来并尝试解析它。
Here is the basic illustration

Bundle bundle = intent.getExtras();
if (bundle != null) {
    for (String key : bundle.keySet()) {
        Object value = bundle.get(key);
        Log.d("FCM", "Key: " + key + " Value: " + value);
    }
}


第 2 步:删除应用在后台时由 Firebase 库创建的通知
为什么:
我们可以创建我们的自定义通知。但是Firebase库创建的通知仍然存在(实际上它是由“”super.handleIntent(intent)“”创建的。下面有详细说明。)。然后我们会有两个通知。那是相当奇怪的。所以我们必须删除 Firebase 库创建的通知

如何(项目构建级别为 Android 6.0 以上):
识别我们要删除的通知并获取信息。并使用“notificationManager.cancel()”删除它们。

private void removeFirebaseOrigianlNotificaitons() {

    //check notificationManager is available
    NotificationManager notificationManager = 
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    if (notificationManager == null )
        return;

    //check api level for getActiveNotifications()
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        //if your Build version is less than android 6.0
        //we can remove all notifications instead. 
        //notificationManager.cancelAll();
        return;
    }


    //check there are notifications
    StatusBarNotification[] activeNotifications = 
        notificationManager.getActiveNotifications();
    if (activeNotifications == null)
        return;

    //remove all notification created by library(super.handleIntent(intent))
    for (StatusBarNotification tmp : activeNotifications) {
        Log.d("FCM StatusBarNotification", 
            "StatusBarNotification tag/id: " + tmp.getTag() + " / " + tmp.getId());
        String tag = tmp.getTag();
        int id = tmp.getId();

        //trace the library source code, follow the rule to remove it.
        if (tag != null && tag.contains("FCM-Notification"))
            notificationManager.cancel(tag, id);
    }
}

我的完整示例代码:

public class MyFirebaseMessagingService extends FirebaseMessagingService {

private static int notificationCount=0;

@Override
public void handleIntent(Intent intent) {
    //add a log, and you'll see the method will be triggered all the time (both foreground and background).
    Log.d( "FCM", "handleIntent");

    //if you don't know the format of your FCM message,
    //just print it out, and you'll know how to parse it
    Bundle bundle = intent.getExtras();
    if (bundle != null) {
        for (String key : bundle.keySet()) {
            Object value = bundle.get(key);
            Log.d("FCM", "Key: " + key + " Value: " + value);
        }
    }

    //the background notification is created by super method
    //but you can't remove the super method. 
    //the super method do other things, not just creating the notification
    super.handleIntent(intent);

    //remove the Notificaitons
    removeFirebaseOrigianlNotificaitons();

    if (bundle ==null)
        return;

    //pares the message
    CloudMsg cloudMsg = parseCloudMsg(bundle);

    //if you want take the data to Activity, set it
    Bundle myBundle = new Bundle();
    myBundle.putSerializable(TYPE_FCM_PLATFORM, cloudMsg);
    Intent myIntent = new Intent(this, NotificationActivity.class);
    myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    myIntent.putExtras(myBundle);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, notificationCount, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    //set the Notification
    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.mipmap.icon)
            .setContentTitle(cloudMsg.getTitle())
            .setContentText(cloudMsg.getMessage())
            .setAutoCancel(true)
            .setContentIntent(pendingIntent);

    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.notify(notificationCount++, notificationBuilder.build());
}



/**
 * parse the message which is from FCM
 * @param bundle
 */
private CloudMsg parseCloudMsg(Bundle bundle) {
    String title = null, msg=null;

    //if the message is sent from Firebase platform, the key will be that
    msg = (String) bundle.get("gcm.notification.body");

    if(bundle.containsKey("gcm.notification.title"))
    title = (String) bundle.get("gcm.notification.title");

    //parse your custom message
    String testValue=null;
    testValue =  (String) bundle.get("testKey");

    //package them into a object(CloudMsg is your own structure), it is easy to send to Activity.
    CloudMsg cloudMsg = new CloudMsg(title, msg, testValue);
    return cloudMsg;
}


/**
 * remove the notification created by "super.handleIntent(intent)"
 */
    private void removeFirebaseOrigianlNotificaitons() {

    //check notificationManager is available
    NotificationManager notificationManager = 
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    if (notificationManager == null )
        return;

    //check api level for getActiveNotifications()
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        //if your Build version is less than android 6.0
        //we can remove all notifications instead. 
        //notificationManager.cancelAll();
        return;
     }

    //check there are notifications
    StatusBarNotification[] activeNotifications = 
        notificationManager.getActiveNotifications();
    if (activeNotifications == null)
        return;

    //remove all notification created by library(super.handleIntent(intent))
    for (StatusBarNotification tmp : activeNotifications) {
        Log.d("FCM StatusBarNotification", 
            "tag/id: " + tmp.getTag() + " / " + tmp.getId());
        String tag = tmp.getTag();
        int id = tmp.getId();

        //trace the library source code, follow the rule to remove it.
        if (tag != null && tag.contains("FCM-Notification"))
            notificationManager.cancel(tag, id);
    }
}
}

【讨论】:

  • 这个解决方案不好。如果您的应用程序没有运行,您将不会收到 antyhing。这就是为什么 Google 已将其从 api 中删除,不需要它。正确的方法是使用 firebase 通知(如下所述)或同步适配器/后台服务。
  • 也许你可以追踪库源(handleIntent())。您提到的 firebase 通知也是由 super.handleIntent() 创建的。因此,如果应用程序处于后台时未触发“handleIntent()”。 firebase 库如何创建“firebase 通知”
  • 我猜它是由 Google Play Services 处理的,而不是您的应用程序本身
  • 怎么样?帮我个忙。做一个简单的测试。您覆盖 handleIntent() 但不要调用 super.handleIntent() 并尝试发送您的 FCM。如果您有时间,请在完成测试后回复。
  • 这是一个聪明的主意。但是看起来在 17.x.x 版本的 firebase-messaging 库中,FirebaseMessagingService 不再有 handleIntent()?
【解决方案2】:

但是,如果我在应用程序处于后台时无能为力,则无法创建自定义通知。 (例如,用户点击通知后会弹出一个窗口显示详细信息。)

那么当应用在后台时,我如何使用 FCM 处理通知?

首先,您需要创建发送到 fcm 服务器的正确消息负载。示例:

{
  "to": "topic_name",
  "priority": "high",
  "data": {
    "field1": "field1 value" 
    "field2": "field2 value" 
  }

  "notification" : {
      "body" : "Lorem ipsum",
      "title" : "sampke title" 
      "click_action": "SHOW_DETAILS" 
    }
}

data payload 是您希望在用户点击通知后显示为消息详细信息的实际数据,notification payload 表示生成的通知的外观(可以设置更多属性),您不需要自己构建通知,这里只需要设置属性即可。

要在用户点击通知后显示您的活动,您需要设置与click_action对应的意图过滤器:

<intent-filter>
     <action android:name="SHOW_DETAILS"/>
     <category android:name="android.intent.category.DEFAULT"/>
 </intent-filter>

当用户点击通知时,具有上述意图过滤器的活动将自动启动。 最后一步是在点击通知后启动活动时检索数据。这很容易。自定义数据通过 bundle 传递给活动。在您的活动的onCreate 方法中执行类似的操作:

Bundle bundle = getIntent().getExtras();
if(bundle.getString("action").equals("SHOW_DETAILS")) /*This indicates activity is launched from notification, not directly*/
{
 //Data retrieved from notification payload send 
 String filed1 = bundle.getString("field1");
 String filed2 = bundle.getString("field2");
}

如果应用程序未运行或处于后台,上述所有内容均有效。如果您的应用程序在前台,则不会创建任何通知。相反,您将收到onMessageReceived() 事件,因此您可以在那里处理相同的数据(我想您知道如何)。

参考:

https://firebase.google.com/docs/cloud-messaging/http-server-ref https://github.com/firebase/quickstart-android/tree/master/messaging

【讨论】:

  • 感谢您的解释。但是使用 FCM 控制台发送云消息时如何处理呢?创建一个 Web UI 让控制器发送它?
  • AFAIK 您无法使用 Firebase 控制台设置点击操作。要从您的计算机发送消息,我建议使用 curl
  • 如何处理像“onMessageReceived()”这样的“背景情况”?我想在创建通知之前做一些事情。例如,设置 JobScheduler,向服务器报告一些内容,将一些内容记录到数据库中。如果用户不点击通知只是滑动它,我该如何设置和做这些事情?
  • 你不能。如果您确实需要,请使用上面提到的@Ibrahim 同步适配器并发送静默推送,无需通知有效负载。不过这需要做更多的工作,您需要自己设置同步适配器、内容提供程序并构建通知(实际上,同步适配器会创建通知,而不是您的应用)。
  • 检查这个,看看如何使用数据负载并在onMessageRecieved()接收它:stackoverflow.com/questions/48301350/…
【解决方案3】:

您需要使用 FCM 数据消息才能在 android 应用中创建自定义通知。即使您的应用在后台,onMessageReceived 也会被调用,因此您可以处理数据并显示自定义通知。

https://firebase.google.com/docs/cloud-messaging/android/receive

需要从服务器发送的数据报文格式:

{"message":{
"token":"Your Device Token",
"data":{
  "Nick" : "Mario",
  "body" : "great match!",
  "Room" : "PortugalVSDenmark"
}
}
}

【讨论】:

  • 我认为如果我们这样做,在 iOS 和 Android 上都使用 Firebase 的人会遇到 iOS 部分的问题,因为据我所知 iOS 需要 JSON 中的通知块。如果我错了,请纠正我!
  • 我想是的,但是你有没有找到任何解决方案.. android, ios
【解决方案4】:

FCM 如果您的应用程序被终止,则不会发送后台通知,正如您在 answer 中描述的 handleIntent() 解决方案它可能适用于某些设备和某些设备FCM 的旧版本,如果你 @override 没有在 firebase 官方文档中描述的方法,你可能会在这里遇到一些问题,你可以在 own risk! 上使用它。

解决办法是什么?

您需要在 FCM 旁边使用自己的推送通知服务,例如 Telegram

或者在 GCM 旁边使用 SyncAdapter,例如 Gmail

因此,如果您需要它像那些应用程序一样成功运行,您必须使用您自己的 hack

【讨论】:

  • 谢谢。这确实是个问题。但我只是在项目决定使用 FCM 的情况下解决它
  • 我可以向关闭的应用发送通知。这是错误引导
猜你喜欢
  • 2016-11-21
  • 2021-10-17
  • 1970-01-01
  • 2018-09-07
  • 2017-06-25
  • 2022-01-01
  • 1970-01-01
  • 2020-09-01
  • 2020-05-17
相关资源
最近更新 更多