Firebase在应用程序后台时未调用onMessageReceived方法

326

我正在使用Firebase并测试从我的服务器向我的应用程序发送通知,即使应用程序在后台运行。通知已成功发送,甚至出现在设备的通知中心中,但是当通知出现或者我点击它时,我的FCMessagingService中的onMessageReceived方法从未被调用。

当我在应用程序前台测试时,onMessageReceived方法被调用,一切正常。问题发生在应用程序在后台运行时。

这是预期行为吗,还是有办法解决这个问题?

这是我的FBMessagingService:

import android.util.Log;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class FBMessagingService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Log.i("PVL", "MESSAGE RECEIVED!!");
        if (remoteMessage.getNotification().getBody() != null) {
            Log.i("PVL", "RECEIVED MESSAGE: " + remoteMessage.getNotification().getBody());
        } else {
            Log.i("PVL", "RECEIVED MESSAGE: " + remoteMessage.getData().get("message"));
        }
    }
}

除了 JSON 主体之外,你的 onTokenRefresh 代码在哪里?你是否已经完成了 Android 设置 - Kato
4
你的意思是通知的JSON主体吗?此外,我的onTokenRefresh代码位于FirebaseInstanceID服务中。 - Cyogenos
你能发布一下你正在发送的示例负载吗? - AL.
你也可以查看这个线程 http://stackoverflow.com/questions/39046270/google-fcm-getintent-dont-returning-expected-data-when-app-is-in-background-stat - Md. Sajedul Karim
有两种类型的 FCM。https://dev59.com/JVoU5IYBdhLWcg3wM08H#37845174 - Zar E Ahmer
显示剩余4条评论
31个回答

199

这是按照预期工作的,通知消息只在您的应用程序在前台时传递到您的onMessageReceived回调函数中。如果您的应用程序在后台或关闭状态下,则会在通知中心显示通知消息,并将任何来自该消息的数据传递给用户点击通知后启动的intent。

您可以在JSON中指定click_action,以指示用户点击通知时应启动的intent。如果未指定click_action,则使用主活动。

当启动intent时,您可以使用

getIntent().getExtras();

检索一个包含与通知消息一起发送的任何数据的 Set。

有关通知消息的更多信息,请参见文档


7
使用 Firebase 通知控制台时,是否有设置 click_action 的方法? - Nii Laryea
7
好的,但如果应用程序被杀死(无前景或背景)怎么办? - michalu
4
如何禁用用户丢弃通知?因为如果用户丢弃它,这意味着所有数据都将被跳过……对吗? - Sirop4ik
4
没问题!因此,当向Android发送通知消息时,附带的数据应该是增强通知体验的数据。它不应该是应用程序关键数据,对于即使用户关闭通知也需要应用程序使用的数据,请使用数据消息。 - Arthur Thompson
15
这并不完全正确。如果消息只包含数据而不是通知负载,则无论应用程序是否在前台,消息都将始终传递到onMessageReceive。 - JacksOnF1re
显示剩余17条评论

190

完全从您的服务器请求中删除notification字段。只发送data并在onMessageReceived()中处理它,否则当应用程序在后台或被杀死时,onMessageReceived()将不会触发。

不要忘记在通知请求中包含"priority": "high"字段。根据文档:数据消息以普通优先级发送,因此它们不会立即到达;这也可能是问题所在。

这是我从服务器发送的内容

{
  "data":{
    "id": 1,
    "missedRequests": 5
    "addAnyDataHere": 123
  },
  "to": "fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......",
  "priority": "high"
}

因此,您可以在onMessageReceived(RemoteMessage message)中接收数据,就像这样......假设我需要获取ID。

Object obj = message.getData().get("id");
        if (obj != null) {
            int id = Integer.valueOf(obj.toString());
        }

11
我发现,如果我仅发送数据消息,则被从任务管理器清除的应用程序将无法收到通知。这是一种预期行为吗? - Prabhjot Singh
这是否意味着我们无法在控制台中处理通知数据,如果我们发送一个没有通知部分的纯HTTP请求,则可以处理通知数据?我开始使用Firebase控制台,期望我可以做到这一点,所以我要回到HTTP POST通知 :-( - Javier Delgado
9
在 Oreo 版本中,当应用程序被终止时,onMessageReceived 方法不会被调用。我只收到包含数据的负载(payload),你有任何更新吗? - Samir Mangroliya
2
嗨,但是当应用程序完全关闭时,它不起作用。 - C Williams
1
我在应用程序的onCreate中编写了一些代码,如果应用程序被杀死并且我收到通知,则会崩溃。我还发现,在运行应用程序并直接杀死它后,您不会收到通知。我再次打开应用程序并将其杀掉后,才能收到通知。很奇怪。 - coolcool1994
显示剩余4条评论

69

这个方法handleIntent()已经被弃用了,因此可以按照以下方式处理通知:

  1. 前台状态:点击通知将转至待处理的PendingIntent activity,该activity通常与通知数据有效载荷一起以编程方式创建。

  2. 后台/关闭状态:在这种情况下,系统会根据通知有效负载自动创建通知,点击该通知将带您到应用程序的启动器活动,您可以轻松地在任何生命周期方法中获取Intent数据。


1
谢谢!!! 我在这个问题上浪费了几天时间,而这个解决方法挽救了我。 - Igor Janković
4
我正在handleIntent(Intent intent)中处理通知显示逻辑,但是当应用程序在后台时,会显示2个通知,一个是我创建的,另一个是默认的,其中包含整个通知消息。 - Joyson
5
很棒,但是我不认为在这种情况下需要使用OnMessageReceived函数!? - Alaa AbuZarifa
我无法在handleIntent(Intent intent)方法中获取有效载荷。 - ashish
8
我正在使用com.google.firebase:firebase-messaging:11.6.2,并且现在的handleIntent是final的。请查看https://dev59.com/xabja4cB1Zd3GeqPlsg1。 - Ronak Poriya
显示剩余10条评论

44

这里有关于 Firebase 消息更明确的概念。 我从他们的支持团队找到了这些信息。

Firebase 有三种消息类型:

通知消息:通知消息在后台或前台都可以运行。当应用处于后台时,通知消息会被发送到系统托盘中。如果应用在前台,则消息由onMessageReceived()didReceiveRemoteNotification回调处理。这些本质上是所谓的显示消息。

数据消息:在 Android 平台上,数据消息可在后台和前台工作。数据消息将由 onMessageReceived() 处理。这里需要注意一点:在 Android 上,可以通过启动活动使用的 Intent 中检索数据有效负载。简而言之,如果您有 “click_action”:“launch_Activity_1”,则只能通过 Activity_1getIntent() 检索此意图。

同时具有通知和数据负载的消息:当应用程序在后台运行时,将在通知栏中接收通知负载,仅在用户点击通知时处理数据负载。当应用程序在前台运行时,您的应用程序将收到一个带有两个有效负载的消息对象。其次,click_action参数通常在通知负载中使用而不在数据负载中使用。如果在数据负载内使用此参数,则该参数将被视为自定义键值对,因此您需要实现自定义逻辑使其按预期工作。

此外,我建议您使用onMessageReceived方法(请参见数据消息)来提取数据包。从您的逻辑中,我检查了bundle对象,并没有找到预期的数据内容。这里是一个类似情况的参考,可能会提供更多的清晰度。

从服务器端看,Firebase通知应遵循以下格式:

服务器端应发送"notification"对象。在我的TargetActivity中缺少"notification"对象,无法使用getIntent()获取消息。

正确的消息格式如下:

{
 "data": {
  "body": "here is body",
  "title": "Title"
 },
"notification": {
  "body": "here is body",
  "title": "Title",
  "click_action": "YOUR_ACTION"
 },
 "to": "ffEseX6vwcM:APA91bF8m7wOF MY FCM ID 07j1aPUb"
}

了解更多信息请访问https://dev59.com/KlkS5IYBdhLWcg3wvI20#39805517


6
值得一提的是,如果设备处于深度休眠模式(在Android 7.0中引入),将无法接收到“数据消息”。对此要小心! - Sameer J

32

我有同样的问题。使用“数据消息”而不是“通知”会更容易。数据消息总是在onMessageReceived类中加载。

在那个类中,您可以使用notificationbuilder创建自己的通知。

例如:

 @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        sendNotification(remoteMessage.getData().get("title"),remoteMessage.getData().get("body"));
    }

    private void sendNotification(String messageTitle,String messageBody) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0 /* request code */, intent,PendingIntent.FLAG_UPDATE_CURRENT);

        long[] pattern = {500,500,500,500,500};

        Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);

        NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_stat_name)
                .setContentTitle(messageTitle)
                .setContentText(messageBody)
                .setAutoCancel(true)
                .setVibrate(pattern)
                .setLights(Color.BLUE,1,1)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }

7
谢谢。我修改了我的服务器代码,使用 "data" 替代了 "notification",现在它完美地工作了。 - Mahesh Kavathiya
5
只有在应用程序在前台时才能使其工作,如果在后台则无法工作。你能帮我在这两种情况下触发此事件吗? - Anant Shah
5
实际上这里有三种可能的情况。 1)应用在前台。 2)应用在后台。3)应用未运行。 正如您所说,第一种和第二种情况将收到“数据”消息,但是第三种情况不会,当应用未运行时。为了满足所有三种情况,您需要在消息中设置“通知”字段。(如果您想支持iOS和Android客户端,这也是一个好主意) - Steve Moseley
@SteveMoseley您是否有这三种情况的文档链接?我在文档中只看到“前景”和“后台”,并认为后台包括“应用程序未运行”的情况,因为他们会这样宣传:“发送通知以重新吸引用户使用您的应用程序,从而赚取收益”<--如果仅在应用程序运行时接收通知,则不需要“重新吸引”用户。但是我在我的真实设备上经历的正是您描述的这三种情况。(模拟器运行良好) - Micha F.
1
即使应用程序没有运行,我仍然会在onmessagereceived函数中收到来自服务器的消息。我同意您的观点,如果您想支持iOS,最好使用“通知”。 - Koot
显示剩余2条评论

30
根据 Firebase Cloud Messaging 的文档,如果 Activity 在前台,则会调用 onMessageReceived 方法。如果 Activity 在后台或关闭状态,则通知消息将在应用程序启动器活动的通知中心中显示。 如果您的应用程序在后台运行,则可以通过调用 Firebase 消息的 REST 服务API 来在单击通知时调用自定义活动,方法如下:

URL- https://fcm.googleapis.com/fcm/send

Method Type- POST

Header- Content-Type:application/json
Authorization:key=your api key

正文/载荷:

{ "notification": {
    "title": "Your Title",
    "text": "Your Text",
     "click_action": "OPEN_ACTIVITY_1" // should match to your intent filter
  },
    "data": {
    "keyname": "any value " //you can get this data as extras in your activity and this data is optional
    },
  "to" : "to_id(firebase refreshedToken)"
} 

如果你想在你的应用中使用这个功能,你可以在你的Activity中添加以下代码:

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

我应该在哪里创建意图并使其打开特定的活动?我知道这会在清单中注册OPEN_ACTIVITY_1意图,但我实际上在哪里调用它呢? - Cyogenos
1
请查看此链接:https://firebase.google.com/docs/cloud-messaging/downstream#sample-receive - Ankit Adlakha
我们应该从意图过滤器中调用一个活动吗?还是在onMessageReceived中手动启动它? - CoolMind

22

onMessageReceived(RemoteMessage remoteMessage) 方法基于以下情况调用。

  • FCM响应 带有 notificationdata 块:
{
  
"to": "device token list",
  "notification": {
    "body": "Body of Your Notification",
    "title": "Title of Your Notification"
  },
  "data": {
    "body": "Body of Your Notification in Data",
    "title": "Title of Your Notification in Title",
    "key_1": "Value for key_1",
    "image_url": "www.abc.com/xyz.jpeg",
    "key_2": "Value for key_2"
  }
}
  1. 前台应用:

onMessageReceived(RemoteMessage remoteMessage) 被调用,将在通知栏中显示大图标和大图片。我们可以从通知数据块中读取内容。

  1. 后台应用:

onMessageReceived(RemoteMessage remoteMessage) 不会被调用,系统托盘将接收消息并从通知块中读取主体和标题,并在通知栏中显示默认的消息和标题。

  • 仅有数据块的 FCM 响应:

在这种情况下,从 json 中删除通知块。

{
  
"to": "device token list",
  "data": {
    "body": "Body of Your Notification in Data",
    "title": "Title of Your Notification in Title",
    "key_1": "Value for key_1",
    "image_url": "www.abc.com/xyz.jpeg",
    "key_2": "Value for key_2"
  }
}

调用onMessageReceived()的解决方案:

  1. 应用在前台:

onMessageReceived(RemoteMessage remoteMessage) 被调用,通知栏显示 LargeIcon 和 BigPicture。我们可以从notificationdata块中读取内容。

  1. 应用在后台:

onMessageReceived(RemoteMessage remoteMessage) 被调用,系统托盘将不会收到消息,因为响应中没有notification键。通知栏显示 LargeIcon 和 BigPicture。

代码

 private void sendNotification(Bitmap bitmap,  String title, String 
    message, PendingIntent resultPendingIntent) {

    NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle();
    style.bigPicture(bitmap);

    Uri defaultSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);

    NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    String NOTIFICATION_CHANNEL_ID = mContext.getString(R.string.default_notification_channel_id);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "channel_name", NotificationManager.IMPORTANCE_HIGH);

        notificationManager.createNotificationChannel(notificationChannel);
    }
    Bitmap iconLarge = BitmapFactory.decodeResource(mContext.getResources(),
            R.drawable.mdmlogo);
    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(mContext, NOTIFICATION_CHANNEL_ID)
            .setSmallIcon(R.drawable.mdmlogo)
            .setContentTitle(title)
            .setAutoCancel(true)
            .setSound(defaultSound)
            .setContentText(message)
            .setContentIntent(resultPendingIntent)
            .setStyle(style)
            .setLargeIcon(iconLarge)
            .setWhen(System.currentTimeMillis())
            .setPriority(Notification.PRIORITY_MAX)
            .setChannelId(NOTIFICATION_CHANNEL_ID);


    notificationManager.notify(1, notificationBuilder.build());


}

参考链接:

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


对于遇到后台问题的人来说,最重要的部分是从服务器发送的JSON中删除“notification”属性。这样可以解决问题。 非常感谢。 - Ramy M. Mousa

16
如果应用程序处于后台模式或非活动状态(已被杀死),并且您在通知上点击,则应检查LaunchScreen中的有效负载(在我的情况下,启动屏幕是MainActivity.java)。
因此,在MainActivity.javaonCreate中,请检查Extras
    if (getIntent().getExtras() != null) {
        for (String key : getIntent().getExtras().keySet()) {
            Object value = getIntent().getExtras().get(key);
            Log.d("MainActivity: ", "Key: " + key + " Value: " + value);
        }
    }

我该如何获取通知的标题、正文和数据? - Md.Tarikul Islam
1
谢谢,这就是答案。@Md.Tarikul Islam使用onMessageReceived(),如同在这个问题中提到的。 - felixwcf

13

我遇到了同样的问题。如果应用程序在前台 - 它会触发我的后台服务,在那里我可以根据通知类型更新我的数据库。 但是,如果应用程序进入后台 - 默认的通知服务将负责向用户显示通知。

以下是我解决方法,用于识别应用程序是否在后台并触发您的后台服务:

public class FirebaseBackgroundService extends WakefulBroadcastReceiver {

  private static final String TAG = "FirebaseService";

  @Override
  public void onReceive(Context context, Intent intent) {
    Log.d(TAG, "I'm in!!!");

    if (intent.getExtras() != null) {
      for (String key : intent.getExtras().keySet()) {
        Object value = intent.getExtras().get(key);
        Log.e("FirebaseDataReceiver", "Key: " + key + " Value: " + value);
        if(key.equalsIgnoreCase("gcm.notification.body") && value != null) {
          Bundle bundle = new Bundle();
          Intent backgroundIntent = new Intent(context, BackgroundSyncJobService.class);
          bundle.putString("push_message", value + "");
          backgroundIntent.putExtras(bundle);
          context.startService(backgroundIntent);
        }
      }
    }
  }
}

在 manifest.xml 文件中

<receiver android:exported="true" android:name=".FirebaseBackgroundService" android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            </intent-filter>
        </receiver>

在最新的Android 8.0版本中测试了这个解决方案。谢谢!


这个FirebaseBackgroundService类在哪里使用?@Nagendra Badiganti - user1731387
在哪里使用这段代码:public class FirebaseBackgroundService extends WakefulBroadcastReceiver @Nagendra Badiganti - user1731387
在您的包中创建一个服务类并在manifest.xml中注册。确保为您的通知设置了过滤器,因为该服务会触发每个GCM通知。 - Nagendra Badiganti
你在项目根目录中添加了google-services.json文件吗? - Nagendra Badiganti
1
自API级别26.1.0起,WakefulBroadcastReceiver已被弃用。 - Minoru
显示剩余2条评论

10

覆盖FirebaseMessageServicehandleIntent方法对我有用。

这里是C#(Xamarin)中的代码:

public override void HandleIntent(Intent intent)
{
    try
    {
        if (intent.Extras != null)
        {
            var builder = new RemoteMessage.Builder("MyFirebaseMessagingService");

            foreach (string key in intent.Extras.KeySet())
            {
                builder.AddData(key, intent.Extras.Get(key).ToString());
            }

            this.OnMessageReceived(builder.Build());
        }
        else
        {
            base.HandleIntent(intent);
        }
    }
    catch (Exception)
    {
        base.HandleIntent(intent);
    }
}

这就是Java的代码。

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

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

handleIntent() 是 final。 - Ettore Gallina
@Ettore 我该如何调用 handleIntent 函数? - Hiroto
当您从Firebase接收到消息时,将调用该方法,但是您无法覆盖该方法,因为该方法被声明为final。(Firebase 11.6.0) - Ettore Gallina

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