如何使用Firebase Cloud Messaging发送设备到设备的消息?

80

在搜索文档后,我没有找到任何关于如何在不使用外部服务器的情况下使用 FCM 发送设备到设备消息的信息。

例如,如果我正在创建一个聊天应用程序,我需要向用户发送推送通知以告知未读消息,因为他们不会一直在线,我也无法在后台运行一个持久化服务,该服务将始终连接到实时数据库,因为那将消耗过多资源。

那么,当某个用户“B”向用户“A”发送聊天消息时,我应该如何向用户“A”发送推送通知?是否需要外部服务器来完成这项工作,还是只能使用 Firebase 服务器?


3
我尚未使用过FCM,但我曾使用过GCM。假设FCM与GCM几乎相同,设备A将消息发送至服务器,服务器会将消息推送到设备B。请查看https://firebase.google.com/support/faq/#messaging-difference。 - j4rey
1
@j4rey89 是的,我知道可以使用外部服务器来完成。但我想问一下是否可以不使用外部服务器,因为那样需要我维护和支付两个服务器的费用,而不是一个。 - Suyash
7
为了在你的设备之间发送FCM消息,必须运行自己的服务器。如果你担心运行服务器的成本,可以开始部署到具有免费配额的Openshift Online(PaaS)或Google AppEngine(也是PaaS)。@Suyash - MrBrightside
2
@j4rey89 MrBrightside:听起来像是一个答案。 :-) - Frank van Puffelen
1
请参阅:https://firebase.googleblog.com/2016/08/sending-notifications-between-android.html - Micro
显示剩余4条评论
14个回答

41

更新:现在可以使用 Firebase 云函数作为处理推送通知的服务器。在这里查看他们的文档

============

根据文档,您必须实现一个服务器来处理设备到设备通信中的推送通知。

在编写使用 Firebase 云消息传递的客户端应用程序之前,您必须拥有一个满足以下标准的应用服务器:

...

您需要决定要使用哪些 FCM 连接服务器协议,以使您的应用服务器与 FCM 连接服务器进行交互。请注意,如果您想要从客户端应用程序使用上行消息,则必须使用 XMPP。有关此更详细的讨论,请参见选择 FCM 连接服务器协议

如果您只需要从服务器向用户发送基本通知,则可以使用他们的无服务器解决方案 Firebase 通知

在这里查看 FCM 和 Firebase 通知之间的比较:https://firebase.google.com/support/faq/#messaging-difference


1
好的答案。你知道有什么教程或视频可以解释如何做到这一点吗?谢谢。 - Ben Akin
4
请访问 https://firebase.googleblog.com/2016/08/sending-notifications-between-android.html。 - Frank van Puffelen
你能帮我理解一下吗?据我所知,如果我需要从一个用户发送直接消息给另一个用户,我必须使用HTTP将此消息发送到我的服务器,接下来我的服务器将使用FCM向接收者发送通知,因此接收者可以使用发送方的ID检索数据。接收者接下来会连接到FCM,并通过该ID从FCM数据库中检索所有数据?是这样吗? - Sirop4ik
1
完美的答案,我已经研究了两天。非常详细地介绍了FCM和服务器是否需要等信息。谢谢! - Khang Tran

29
使用必需的标题和数据在链接 https://fcm.googleapis.com/fcm/send 上发出 HTTP POST 请求对我很有帮助。在下面的代码片段中,Constants.LEGACY_SERVER_KEY 是一个本地类变量,在 Firebase 项目的 设置->云消息传递->Legacy Server key 中可以找到。在下面的代码片段中,需要传递设备注册令牌,即 regToken,请参阅此处
最后,您需要依赖 okhttp 库才能使此片段工作。
public static final MediaType JSON
        = MediaType.parse("application/json; charset=utf-8");
private void sendNotification(final String regToken) {
    new AsyncTask<Void,Void,Void>(){
        @Override
        protected Void doInBackground(Void... params) {
            try {
                OkHttpClient client = new OkHttpClient();
                JSONObject json=new JSONObject();
                JSONObject dataJson=new JSONObject();
                dataJson.put("body","Hi this is sent from device to device");
                dataJson.put("title","dummy title");
                json.put("notification",dataJson);
                json.put("to",regToken);
                RequestBody body = RequestBody.create(JSON, json.toString());
                Request request = new Request.Builder()
                        .header("Authorization","key="+Constants.LEGACY_SERVER_KEY)
                        .url("https://fcm.googleapis.com/fcm/send")
                        .post(body)
                        .build();
                Response response = client.newCall(request).execute();
                String finalResponse = response.body().string();
            }catch (Exception e){
                //Log.d(TAG,e+"");
            }
            return null;
        }
    }.execute();

}

如果您希望向特定主题发送消息,请按照以下方式在JSON中替换 regToken

json.put("to","/topics/foo-bar")

不要忘记在AndroidManifest.xml文件中添加INTERNET权限。

重要提示:使用上面的代码意味着您的服务器密钥驻留在客户端应用程序中。这是危险的,因为有人可以挖掘您的应用程序并获取服务器密钥以向您的用户发送恶意通知。


10
缺点是您的服务器密钥保存在客户端应用程序中。这很危险,因为有人可以深入挖掘您的应用程序并获取服务器密钥,从而向您的用户发送恶意通知。这就是为什么您永远不应该这样做的原因。 - kirtan403
1
@Mr.Popular 也许吧,但如果有人能反编译你的代码(当然,他们可以),那么他们就可以获取你用来加密服务器密钥的东西,并获得你的服务器密钥。然后他们就可以向任何人发送通知,而没有任何限制...所以在客户端上放置服务器密钥是一个非常糟糕的想法。非常非常糟糕... - kirtan403
大家好,请告诉我什么是regToken,如何生成它,以及Constants.LEGACY_SERVER_KEY是什么。请帮我解决这个问题。 - Qutbuddin Bohra
什么是regToken? - Kaushik Makwana
1
@Tabish,请使用remoteMessage.getNotification()。我们这里不发送数据。 - Brijesh Kumar
显示剩余9条评论

5
你可以使用Volley Jsonobject请求来完成。首先按照以下步骤操作:
1. 复制传统服务器密钥并将其存储为Legacy_SERVER_KEY。 Legacy服务器密钥可以在图片中看到如何获取。
2. 您需要Volley依赖项。 compile 'com.mcxiaoke.volley:library:1.0.19'

enter image description here

发送推送的代码:

private void sendFCMPush() {

    String Legacy_SERVER_KEY = YOUR_Legacy_SERVER_KEY;
    String msg = "this is test message,.,,.,.";
    String title = "my title";
    String token = FCM_RECEIVER_TOKEN;

    JSONObject obj = null;
    JSONObject objData = null;
    JSONObject dataobjData = null;

    try {
        obj = new JSONObject();
        objData = new JSONObject();

        objData.put("body", msg);
        objData.put("title", title);
        objData.put("sound", "default");
        objData.put("icon", "icon_name"); //   icon_name image must be there in drawable
        objData.put("tag", token);
        objData.put("priority", "high");

        dataobjData = new JSONObject();
        dataobjData.put("text", msg);
        dataobjData.put("title", title);

        obj.put("to", token);
        //obj.put("priority", "high");

        obj.put("notification", objData);
        obj.put("data", dataobjData);
        Log.e("!_@rj@_@@_PASS:>", obj.toString());
    } catch (JSONException e) {
        e.printStackTrace();
    }

    JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.POST, Constants.FCM_PUSH_URL, obj,
            new Response.Listener<JSONObject>() {
                @Override
                public void onResponse(JSONObject response) {
                    Log.e("!_@@_SUCESS", response + "");
                }
            },
            new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.e("!_@@_Errors--", error + "");
                }
            }) {
        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String, String> params = new HashMap<String, String>();
            params.put("Authorization", "key=" + Legacy_SERVER_KEY);
            params.put("Content-Type", "application/json");
            return params;
        }
    };
    RequestQueue requestQueue = Volley.newRequestQueue(this);
    int socketTimeout = 1000 * 60;// 60 seconds
    RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
    jsObjRequest.setRetryPolicy(policy);
    requestQueue.add(jsObjRequest);
}

只需调用 sendFCMPush() 函数即可;


你好,有没有可能将消息发送到已订阅的特定频道? - Suchith
是的,可能需要为此添加标志,根据标志可以向已订阅用户发送推送。 - Rajesh Satvara
@RjzSatvara如果应用程序没有在接收器手机上运行,它会收到消息吗?提前致谢。 - Jaco
1
@Jaco,这是不可能的。你必须用其他方法来处理它。 - Rajesh Satvara

2

是的,不需要任何服务器也可以实现。您可以在客户端创建设备组,然后在组内交换消息。但是有一些限制:

  1. 您必须在设备上使用相同的Google帐户
  2. 您无法发送高优先级消息

参考:Firebase文档,请查看“管理Android客户端应用程序上的设备组”部分。


你仍然需要一个服务器来发送群组消息。 - deviant
不行。组内的任何设备都可以发送消息。 - greywolf82
授权:key=API_KEY 您仍需要服务器密钥。因此,此解决方案不适用于生产。 - deviant
API密钥是Google帐户,通信仅限于单个用户帐户。在评论之前请先尝试。 - greywolf82

2

1)订阅相同的主题名称,例如:

  • ClientA.subcribe("to/topic_users_channel")
  • ClientB.subcribe("to/topic_users_channel")

2)在应用程序内发送消息

GoogleFirebase:如何发送主题消息


2
这是否仍需要在客户端使用授权密钥?这会使其不安全。而且我甚至不知道为每个用户创建一个单独的主题是否是一个好主意。 - Suyash
好主意,但是:主题消息针对吞吐量进行了优化,而不是延迟。如果要快速、安全地向单个设备或小组设备传递消息,请将消息定位到注册令牌,而不是主题。 - Michalsx
@Maxim Firsoff- 如何从FCM控制台或其他方式创建主题? - Ajay Sharma
@AjaySharma 我记得 FMC 控制台没有相关工具,你可以通过编程的方式创建一个主题(请参考上面的伪代码)。 - Maxim Firsoff

2

Google Cloud Functions让从设备到设备发送推送消息成为可能,而无需应用服务器。我已经创建了一个云函数,在数据库中添加新消息时触发它。

这是node.js代码。

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin'); admin.initializeApp();

exports.sendNotification = functions.database.ref('/conversations/{chatLocation}/{messageLocation}')
  .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();

       const toIDUser = original.toID;
       const isGroupChat = original.isGroupChat;

       if (isGroupChat) {
       const tokenss =  admin.database().ref(`/users/${toIDUser}/tokens`).once('value').then(function(snapshot) {

// Handle Promise
       const tokenOfGroup = snapshot.val()

      // get tokens from the database  at particular location get values 
       const valuess = Object.keys(tokenOfGroup).map(k => tokenOfGroup[k]);

     //console.log(' ____________ddd((999999ddd_________________ ' +  valuess );
    const payload = {
       notification: {
                 title:   original.senderName + " :- ",
                 body:    original.content
    }
  };

  return admin.messaging().sendToDevice(valuess, payload);



}, function(error) {

  console.error(error);
});

       return ;
          } else {
          // get token from the database  at particular location
                const tokenss =  admin.database().ref(`/users/${toIDUser}/credentials`).once('value').then(function(snapshot) {
                // Handle Promise
  // The Promise was "fulfilled" (it succeeded).

     const credentials = snapshot.val()



    // console.log('snapshot ......snapshot.val().name****^^^^^^^^^^^^kensPromise****** :- ', credentials.name);
     //console.log('snapshot.....****snapshot.val().token****^^^^^^^^^^^^kensPromise****** :- ', credentials.token);


     const deviceToken = credentials.token;

    const payload = {
       notification: {
                 title:   original.senderName + " :- ",
                 body:    original.content
    }
  };

  return admin.messaging().sendToDevice(deviceToken, payload);


}, function(error) {

  console.error(error);
});


          }





  return ;


    });

1
Google Cloud Functions使得从设备到设备发送推送通知成为可能,无需应用服务器。 从相关页面上了解Google Cloud Functions:
开发人员可以使用Cloud Functions来与应用程序中的用户保持联系并提供有关应用程序的相关信息。例如,考虑一个允许用户在应用程序中关注彼此活动的应用程序。在这样的应用程序中,由实时数据库写入触发的函数来存储新的关注者,可以创建Firebase Cloud Messaging(FCM)通知,让适当的用户知道他们已经获得了新的关注者。
示例:
  1. 该函数会在存储关注者的实时数据库路径上进行写入触发。
  2. 该函数会组合一条消息通过FCM发送。
  3. FCM将通知消息发送到用户的设备。
这是一个演示项目,可以使用Firebase和Google Cloud Functions发送设备到设备的推送通知。

0
你可以使用Firebase实时数据库来实现。你可以创建用于存储聊天的数据结构,并为两个用户的对话线程添加观察者。它仍然是设备-服务器-设备架构,但在这种情况下,开发人员不需要额外的服务器。这使用Firebase服务器。你可以在这里查看教程(忽略UI部分,尽管那也是聊天UI框架的好起点)。 Firebase实时聊天

3
用户不会一直使用该应用程序,而且我们不能在后台使用 Firebase 实时数据库,因为它会保持与服务器的持久套接字连接,这样会对设备的电池影响过大。 - Suyash
我能够使用Smack库在设备之间发送Firebase消息和通知。我在我的Android代码中没有实现任何外部服务器。Smack通过XMPP协议管理连接和传入/传出的消息节。 - i_o

0

1
是的,但这仍然需要我们自己的外部服务器,对吧?因为我们不应该直接在客户端中嵌入 API_KEY。我的问题是是否有可能在没有外部服务器的情况下实现,而目前其他答复所建议的并非如此。 - Suyash

0

这里是如何在不使用除 Firebase 之外的第二个服务器的情况下获取通知的步骤。因此,我们仅使用 Firebase,而不需要额外的服务器。

在移动应用程序代码中,我们使用 Android 库(如 这里)创建自己的通知功能,而不是使用 Firebase 库(如 这里),也不使用 Firebase Cloud Messaging。以下是一个 Kotlin 示例: private fun notification() { createNotificationChannel() val intent = Intent(this, LoginActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val notificationBuilder = NotificationCompat.Builder(this, "yuh_channel_id") .setSmallIcon(R.drawable.ic_send) .setContentText("yuh") .setContentText("yuh") .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(0, notificationBuilder.build())
with(NotificationManagerCompat.from(this)) { notify(0, notificationBuilder.build()) } } private fun createNotificationChannel() { // 只有在 API 26+ 上才创建 NotificationChannel,因为 NotificationChannel 类是新的,不在支持库中 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name = "yuh_channel" val descriptionText = "yuh_description" val importance = NotificationManager.IMPORTANCE_DEFAULT val CHANNEL_ID = "yuh_channel_id" val channel = NotificationChannel(CHANNEL_ID, name, importance).apply { description = descriptionText } // 将通道注册到系统中 val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } }
  • 在 Firebase 数据库中创建“待处理通知”集合。文档应包含用户名称(用于发送通知)和源名称(用户点击通知后应前往的位置)。

  • 在应用程序代码中,实现向“待处理通知”集合添加新记录的选项。例如,如果用户 A 向用户 B 发送消息,则在集合中创建具有用户 B ID(将收到通知的用户)的文档。

  • 在应用程序代码中,设置后台服务(当应用程序对用户不可见时)。就像这里一样。在后台服务中,设置一个监听器以侦听“待处理通知”集合中的更改。当带有用户 ID 的新记录进入集合时,请调用第 1 段所创建的通知函数,并从集合中删除相应的记录。


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