简体   繁体   English

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

[英]How to handle notifications with FCM when app is in either foreground or background

I used firebase to build My project.我用 firebase 来构建我的项目。
It will also use the FCM (firebase cloud message).它还将使用 FCM(firebase 云消息)。
But there is a problem.但有一个问题。
I can't handle the FCM (create my custom notificaion) when app is in background.当应用程序处于后台时,我无法处理 FCM(创建我的自定义通知)。

The official site tutorial said that 官网教程是这么说的
case 1: App foreground -> override the "onMessageReceived()" to create your custom notification.情况 1:应用程序前台 -> 覆盖“onMessageReceived()”以创建您的自定义通知。
case 2: App background -> System will create the notification directly.案例二:App后台->系统会直接创建通知。 We needn't and can't do anything.我们不需要也不能做任何事情。 Because it doesn't trigger the "onMessageReceived()" in this case.因为在这种情况下它不会触发“onMessageReceived()”。

However if I can do nothing when app is background, I can't create my custom notification.但是,如果当应用程序处于后台时我无能为力,我将无法创建自定义通知。 (eg After Users click the notification and it will pop up a window to show detail information.) (例如用户点击通知后会弹出一个window显示详细信息。)

So how do I handle notifications with FCM when app is in background?那么,当应用程序处于后台时,我如何使用 FCM 处理通知?

There is a bad news.有一个坏消息。
Google change the Firebase source code in version 'com.google.firebase:firebase-messaging:11.6.0'. Google 更改了“com.google.firebase:firebase-messaging:11.6.0”版本中的 Firebase 源代码。
handelIntent is "public final void method" now. handelIntent 现在是“public final void 方法”。 which means we can't override it .这意味着我们不能覆盖它。
If you want to use the solution, change the version to be "com.google.firebase:firebase-messaging:11.4.2"如果要使用该解决方案,请将版本更改为“com.google.firebase:firebase-messaging:11.4.2”



Try my way.试试我的方法。 It can perfectly work on the project build version is Android 6.0 above(api level 23) and I have tried it already.它可以完美地在项目构建版本为Android 6.0以上(api级别23)上运行,我已经尝试过了。

There is better way than official site tutorial有比官方网站教程更好的方法

The official site said that the notification will be created by system when app is in background.官网说,当应用程序在后台时,系统会创建通知。 So you can't handle it by overriding the "onMessageReceived()".所以你不能通过覆盖“onMessageReceived()”来处理它。 Because the "onMessageReceived()" is only triggered when app is in foreground.因为“onMessageReceived()”仅在应用程序处于前台时触发。

But the truth is not.但事实并非如此。 Actually the notificaions (when app is in background) are created by Firebase Library.实际上通知(当应用程序在后台时)是由 Firebase 库创建的。

After I traced the firebase library code.在我跟踪了 firebase 库代码之后。 I find a better way.我找到了更好的方法。

Step 1. Override the "handleIntent()" instead of "onMessageReceived()" in FirebaseMessagingService步骤 1. 覆盖 FirebaseMessagingService 中的“handleIntent()”而不是“onMessageReceived()”
why:为什么:
Because the method will be trigger either app is in foreground or the background.因为该方法将触发应用程序在前台或后台。 So we can handle FCM message and create our custom notifications in both cases.因此,我们可以在两种情况下处理 FCM 消息并创建自定义通知。

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


Step 2. Parse the message from FCM步骤 2. 解析来自 FCM 的消息
how:如何:
If you don't know the format of the message you set.如果您不知道您设置的消息格式。 Print it and try to parse it.打印它并尝试解析它。
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);
    }
}


Step 2. Remove the notifications created by Firebase library when the app is in background步骤 2. 删除应用在后台时 Firebase 库创建的通知
why:为什么:
We can create our custom notification.我们可以创建自定义通知。 But the notification created by Firebase Library will still be there (Actually it created by ""super.handleIntent(intent)"". There is detail explaination below.).但是 Firebase 库创建的通知仍然存在(实际上它是由 ""super.handleIntent(intent)"" 创建的。下面有详细说明。)。 Then we'll have two notifcations.然后我们会有两个通知。 That is rather weird.这就比较奇怪了。 So we have to remove the notificaion created by Firebase Library所以我们必须删除 Firebase 库创建的通知

how (project build level is Android 6.0 above):如何(项目构建级别为 Android 6.0 以上):
Recognize the notifications which we want to remove and get the informaion.识别我们要删除的通知并获取信息。 And use the "notificationManager.cancel()" to remove them.并使用“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);
    }
}

The my whole sample code:我的整个示例代码:

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

However if I can do nothing when app is background, I can't create my custom notification.但是,如果当应用程序处于后台时我什么都不做,我就无法创建我的自定义通知。 (eg After Users click the notification and it will pop up a window to show detail information.) (例如,当用户点击通知后,它会弹出一个窗口显示详细信息。)

So how do I handle notifications with FCM when app is in background?那么当应用程序在后台时如何使用 FCM 处理通知?

First, you need to create correct message payload that you send to fcm server.首先,您需要创建发送到 fcm 服务器的正确消息负载。 Example:示例:

{
  "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 is actual data you want to show as message details after user clicks on notification, notification payload represents how generated notification should look (there are much more attributes possible to set), you don't need to build notification by yourself, you only need to set it properties here. data负载是您希望在用户单击通知后显示为消息详细信息的实际数据, notification负载表示生成的通知的外观(可以设置更多属性),您不需要自己构建通知,您只需需要在这里设置它的属性。

To show your activity after user taps on notication, you need to set intent filter corresponding to click_action :要在用户点击通知后显示您的活动,您需要设置与click_action对应的意图过滤器:

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

so activity that have above intent filter will be launched automatically when user taps to notification.因此,当用户点击通知时,将自动启动具有上述意图过滤器的活动。 Last step is to retrieve data when activity is launched after notification tap.最后一步是在点击通知后启动活动时检索数据。 It's pretty easy.这很容易。 Custom data is passed to activity via bundle.自定义数据通过包传递给活动。 Inside onCreate method for your activity do something like that:在您的活动的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");
}

All of above is valid if app is not running or it's in background.如果应用程序未运行或在后台,则上述所有内容均有效。 If your app is foreground, no notification will be created.如果您的应用程序处于前台,则不会创建通知。 Instead, you will receive onMessageReceived() event so you can handle the same data there (I guess you know how).相反,您将收到onMessageReceived()事件,以便您可以在那里处理相同的数据(我想您知道如何)。

Reference:参考:

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

You need to use FCM data messages in order to create custom notification in a android app.Even your app is in background, onMessageReceived will be called, so you can process the data and show the custom notification.您需要使用 FCM 数据消息才能在 android 应用程序中创建自定义通知。即使您的应用程序在后台,也会调用onMessageReceived ,因此您可以处理数据并显示自定义通知。

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

Data message format which has to be sent from server:必须从服务器发送的数据消息格式:

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

FCM Won't send a background notification if your app is killed any more, and as you described in your answer about the handleIntent() solution It may work for some devices and for some old version of the FCM , also if you @override method that doesn't described in the official doc's of firebase you may struggle some problems here, and you use it on your own risk !.如果您的应用程序被handleIntent()FCM将不会发送后台通知,正如您在关于handleIntent()解决方案的回答中所述,它可能适用于某些设备和某些旧版本的FCM ,如果您使用@override方法firebase 的官方文档中没有描述,您可能会在这里遇到一些问题,使用它的风险自负

What is the solution?解决办法是什么?

You need to use your own push-notification-service beside FCM like Telegram .您需要在FCM旁边使用自己的推送通知服务,例如Telegram

OR using SyncAdapter beside GCM like Gmail .或者在GCM旁边使用SyncAdapter ,比如Gmail

So if you need it to work successfully like those apps, you have to use your own hack .因此,如果您需要它像那些应用程序一样成功运行,则必须使用自己的 hack

When your app kill or background never trigger onMessageReceive 当您的应用终止运行或后台运行时, 永远不会触发onMessageReceive

What should do you? 你该怎么办

When push send only add Data items below example; 推送时,仅在以下示例中添加数据项; You can use postman 你可以用邮递员

Firstly need this url : https://fcm.googleapis.com/fcm/send When you send you can choose POST 首先需要以下网址: https : //fcm.googleapis.com/fcm/send发送时,您可以选择POST

After you should add Headers like that: 在添加标题后,应这样:

Content-Type: application/json 内容类型:application / json

Authorization: key="your server key" 授权:key =“您的服务器密钥”

  {
        "to": "/topics/yourTopics",


        "data": {
            "title": "BlaBla",
            "body":"BlaBla"
         }
    }

Handling; 处理;

class PushService : FirebaseMessagingService() {

     override fun onMessageReceived(remoteMessage: RemoteMessage?) {
    for (i in remoteMessage!!.data.keys) {
    //Your maps here you can do something..
    }


        val contentIntent = PendingIntent.getActivity(this,
                0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

        val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
// Your notification set here!! 
        val b = NotificationCompat.Builder(this, "1")
        val n = b.setTicker(remoteMessage!!.data["title"])
                .setWhen(0)
                .setAutoCancel(true)
                .setContentTitle(remoteMessage.data["title"])
                .setContentText(remoteMessage.data["body"])
                .setShowWhen(true)
                .setContentIntent(contentIntent)
                .setSound(uri)
                .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                .setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
                .setStyle(NotificationCompat.BigTextStyle().bigText(remoteMessage.data["body"]))


        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            b.setSmallIcon(R.drawable.ic_push_notification_transparent)
            b.color = ContextCompat.getColor(this, R.color.colorAccent)
        } else {
            b.setSmallIcon(R.mipmap.ic_launcher)
        }

        val notificationManager = this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel("1",
                    "ic_notification",
                    NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        if (notificationManager != null) {
            notificationManager.cancel(1)
            notificationManager.notify(1, n.build())
        }
    }


}

public class FirebaseMessageReceiver extends FirebaseMessagingService {公共 class FirebaseMessageReceiver 扩展 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());
}

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM