当应用程序处于前台或后台时,如何使用FCM处理通知

5
我使用Firebase构建了我的项目,它还将使用FCM(Firebase云消息)。但是有一个问题,当应用程序在后台时,我无法处理FCM(创建自定义通知)。官方网站教程指出:
情况1:应用程序前台 -> 重写“onMessageReceived()”以创建您的自定义通知。
情况2:应用程序后台 -> 系统将直接创建通知,我们不需要也无法做任何事情,因为这种情况下不会触发“onMessageReceived()”。

然而,如果应用程序在后台时我什么都不能做,我就无法创建自定义通知。(例如,用户点击通知后,它将弹出一个窗口显示详细信息。)那么在应用程序后台时如何处理使用FCM的通知呢?

1
你有问题吗,还是只是想说一下你在自己的应用程序中如何处理消息? - Doug Stevenson
@Doug Stevenson 我之前也遇到过这个问题,后来我追踪了 Firebase 库的代码,终于找到了答案。 - Leon Chang
@LeonChang 请看一下这个链接,它可以解决你的问题:https://stackoverflow.com/questions/48301350/android-notifications-when-app-is-in-background/48301893#48301893。如果有效,请告诉我,我们可以将其标记为重复,谢谢! - Peter Haddad
@LeonChang 是的,那是因为你没有像答案中所说的那样在后端使用 data 负载。你需要仅使用 data 负载,并在 FCM 的 onMessageRecieved() 中也需要像答案中一样接收数据负载。例如:你需要在 onMessageRecieved() 中编写以下内容,以便触发 body = remoteMessage.getData().get("bodys"); // bodys 是属性名称 - Peter Haddad
@Peter Haddad 我试过了。虽然我将库更改为最新版本(11.8.0)并添加了“data”有效载荷,但它仍然无法工作。 - Leon Chang
显示剩余3条评论
5个回答

17

有一个坏消息。
谷歌在版本“com.google.firebase:firebase-messaging:11.6.0”中更改了Firebase源代码。
handleIntent现在是“public final void method”。这意味着我们无法覆盖它。
如果您想使用解决方案,请将版本更改为“com.google.firebase:firebase-messaging:11.4.2”



尝试我的方法。它可以在项目构建版本为Android 6.0以上(API级别23)时完美工作,我已经尝试过了。

有比官方网站教程更好的方法

官方网站说,当应用程序处于后台时,通知将由系统创建。因此,您无法通过覆盖“onMessageReceived()”来处理它。因为“onMessageReceived()”仅在应用程序处于前台时触发。

但事实并非如此。实际上,Firebase库会在应用程序处于后台时创建通知。

在追踪Firebase库代码之后,我找到了一种更好的方法。

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

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


步骤2. 解析来自FCM的消息 如何解析: 如果您不知道所设置的消息格式,请打印并尝试解析。 这是基本说明
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);
    }
}
}

4
这个解决方案不太好。如果你的应用程序没有运行,就无法收到任何东西。这就是为什么谷歌已经将其从API中删除,因为它没有必要。正确的方法是使用Firebase通知(如下所述),或者同步适配器/后台服务。 - user1209216
1
也许你可以追踪库源代码(handleIntent())。你提到的Firebase通知也是由super.handleIntent()创建的。那么,如果应用程序在后台时未触发“ handleIntent()”,Firebase库如何创建“ Firebase通知”呢? - Leon Chang
1
怎么样?帮我一个忙。只需进行简单的测试。你重写了handleIntent()但没有调用super.handleIntent(),然后尝试发送你的FCM。如果你有时间,在测试完成后回复一下。 - Leon Chang
我想我不能这样做,因为我的项目使用的是FCM 11.8.0版本,我现在不想降级。 - user1209216
1
这是一个聪明的想法。但是看起来在 firebase-messaging 库的 17.x.x 版本中,FirebaseMessagingService 不再具有 handleIntent() 方法了? - Kevin
显示剩余8条评论

3
然而,如果我的应用程序在后台时无法执行任何操作,我就无法创建自定义通知(例如,用户单击通知后,它会弹出一个窗口显示详细信息)。那么当应用程序在后台时,如何处理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负载是您希望在用户单击通知后显示为消息详细信息的实际数据,notification负载表示如何生成通知应该看起来(有更多属性可以设置),您不需要自己构建通知,只需要在此处设置其属性。

要在用户点击通知后显示您的活动,您需要设置与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让控制器发送吗? - Leon Chang
据我所知,您无法使用Firebase控制台设置单击操作。要从计算机发送消息,我建议使用curl。 - user1209216
我该如何处理像“onMessageReceived()”这样的“后台情况”?在创建通知之前,我想做一些事情。例如,设置JobScheduler,向服务器报告某些内容,将某些内容记录到数据库中。如果用户不点击通知而只是滑动它,我该如何设置并执行这些操作? - Leon Chang
你做不到。如果你确实需要这个功能,就像 @Ibrahim 上面提到的那样使用同步适配器并发送静默推送,不带通知负载。尽管这需要更多的工作,你需要设置同步适配器、内容提供者,并自己构建通知(实际上,同步适配器会创建通知,而不是你的应用程序)。 - user1209216
请查看以下链接,了解如何使用数据有效载荷并在 onMessageReceived() 中接收它:https://stackoverflow.com/questions/48301350/android-notifications-when-app-is-in-background/48301893#48301893 - Peter Haddad

2
为了在 Android 应用程序中创建自定义通知,您需要使用 FCM 数据消息。即使您的应用程序处于后台状态,也会调用 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中包含通知块。如果我错了,请纠正我! - FreakyAli
我认为是这样,但你找到任何解决方案了吗?适用于Android和iOS。 - Md Tariqul Islam

0

FCM不会再在应用程序被杀死时发送后台通知,而且正如您在答案中所描述的handleIntent()解决方案可能适用于某些设备和某些旧版本的FCM,此外,如果您@override方法在firebase的官方文档中没有描述,您可能会在此遇到一些问题,并且在使用时需自行承担风险!。

那么解决方案是什么呢?

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

或者使用SyncAdapter作为GCM的替代品,例如Gmail

因此,如果您需要像这些应用程序一样成功地工作,则必须使用您自己的hack


谢谢。这确实是一个问题。但我只想在项目决定使用FCM的情况下解决它。 - Leon Chang
我可以向已关闭的应用程序发送通知,但这会产生误导。 - Prince Hamza

-1
public class FirebaseMessageReceiver extends FirebaseMessagingService{
    private static final String TAG = "main";
    String s12;
    String channel_id = "general";
    Intent intent;

    @Override
    public void onNewToken(@NonNull String token)
    {
        Log.d(TAG, "Refreshed token: " + token);
    }
    @Override
    public void
    onMessageReceived(RemoteMessage remoteMessage) {
        s12=remoteMessage.getNotification().getClickAction();
        Log.d("tttt",(remoteMessage.getData().toString()));
        Log.d("ttttttt",(remoteMessage.getNotification().toString()));
        if (remoteMessage.getNotification() != null) {
            showNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody());
        }
        //
    }

    public void handleIntent(Intent intent)
    {
        try
        {
            if (intent.getExtras() != null)
            {
                RemoteMessage.Builder builder = new RemoteMessage.Builder("FirebaseMessageReceiver");

                for (String key : intent.getExtras().keySet())
                {
                    builder.addData(key, intent.getExtras().get(key).toString());
                }

                onMessageReceived(builder.build());
            }
            else
            {
                super.handleIntent(intent);
            }
        }
        catch (Exception e)
        {
            super.handleIntent(intent);
        }
    }

    private RemoteViews getCustomDesign(String title, String message) {
        RemoteViews remoteViews = new RemoteViews(getApplicationContext().getPackageName(), R.layout.notification);
        remoteViews.setTextViewText(R.id.title111, title);
        remoteViews.setTextViewText(R.id.message111, message);
        remoteViews.setImageViewResource(R.id.icon111, R.drawable.favicon);
        return remoteViews;
    }
    // Method to display the notifications
    public void showNotification(String title, String message) {
        intent  = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(s12));
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent notifyIntent = PendingIntent.getActivity(this, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        Log.d("notifyy",notifyIntent.toString());
        NotificationCompat.Builder builder
                = new NotificationCompat
                .Builder(getApplicationContext(),
                channel_id)
                .setSmallIcon(R.drawable.favicon)
                .setAutoCancel(true)
                .setVibrate(new long[]{1000, 1000, 1000, 1000, 1000})
                .setOnlyAlertOnce(true)
                .setContentIntent(notifyIntent);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            builder = builder.setContent(getCustomDesign(title, message));
        }
        else {
            builder = builder.setContentTitle(title)
                    .setContentText(message)
                    .setSmallIcon(R.drawable.favicon);
        }
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        // Check if the Android Version is greater than Oreo
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel notificationChannel = new NotificationChannel(channel_id, "web_app",
                    NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(
                    notificationChannel);
        }
        notificationManager.notify(0, builder.build());
    } 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接