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


74

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

例如,如果我正在创建一个聊天应用程序,则我需要向用户发送有关未读消息的推送通知,因为它们不会一直在线,而且我无法在后台拥有持久连接的持久性服务实时数据库,因为那样会占用大量资源。

那么当某个用户“ B”向他/她发送聊天消息时,我该如何向用户“ A”发送推送通知?我是否需要外部服务器,或者仅使用Firebase服务器即可完成?


3
我还没有使用过FCM,....但是我已经使用过GCM...。假设FCM几乎就像GCM .....设备A将消息发送到服务器,服务器会将消息推送到设备B。firebase.google.com/support/faq/#messaging-difference
j4rey

1
@ j4rey89是的,我知道可以使用外部服务器来完成。我在问如果没有它就可以完成,因为那将需要我维护和购买两台服务器,而不是一台。
Suyash

7
@Suyash必须运行您自己的服务器才能在设备之间发送FCM消息。如果您担心运行服务器的成本,则可以开始部署到具有免费配额的Openshift Online(PaaS)或Google AppEngine(也包括PaaS)。
MrBrightside '16

2
@ j4rey89 MrBrightside:听起来像个答案。:-)
弗兰克·范·普菲伦

Answers:


42

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

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

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

在编写使用Firebase Cloud Messaging的客户端应用程序之前,您必须具有满足以下条件的应用程序服务器:

...

您需要确定要使用哪种FCM连接服务器协议来使您的应用服务器与FCM连接服务器进行交互。请注意,如果要使用客户端应用程序中的上游消息传递,则必须使用XMPP。有关此内容的详细讨论,请参阅选择FCM连接服务器协议

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

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


1
好答案。您知道任何可以解释如何执行此操作的教程或视频吗?谢谢
Ben Akin


你能帮我理解吗。据我了解,如果我需要将一个用户的直接消息发送到另一个用户,则必须使用HTTP并将此消息发送到我的服务器,而下一个服务器将使用FCM将通知发送给配方服务器,从而配方服务器检索具有发送者ID的数据。下一步配方连接到FCM,并在ID的帮助下从FCM DB检索所有数据?这样吗
Aleksey Timoshchenko '16

完美的答案,我已经这两天研究了ab。FCM的非常完整的信息以及是否需要。谢谢 !。
功德(Khang Tran)

27

通过链接https://fcm.googleapis.com/fcm/send以及必需的标头和数据发出HTTP POST请求对我有所帮助。在下面的代码片段中 Constants.LEGACY_SERVER_KEY是一个本地类变量,您可以在Firebase Project中找到它Settings->Cloud Messaging->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();

}

此外,如果您要将消息发送到特定主题,请regToken像这样替换json

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

并且不要忘记在AndroidManifest.xml中添加INTERNET权限。

重要说明:-使用上面的代码意味着您的服务器密钥位于客户端应用程序中。这很危险,因为有人可以深入研究您的应用程序并获取服务器密钥以将恶意通知发送给您的用户。


嗨,是否有可能将消息发送到订阅的特定频道?
Suchith

10
缺点是您的服务器密钥位于客户端应用程序中。这很危险,因为有人可以深入研究您的应用程序并获取服务器密钥以将恶意通知发送给您的用户。这就是为什么您永远都不要这样做。
kirtan403

@ kirtan403对客户端中服务器密钥的强加密可以阻止这种情况???
Mr.Popular

@ Mr.Popular也许,但是,如果有人能够反编译您的代码(当然可以),那么他们就可以获取您用来加密服务器密钥的内容并获取服务器密钥。然后他们可以将通知发送给任何人而没有任何限制。因此,将服务器密钥放在客户端是一个非常糟糕的主意。一个非常非常糟糕的主意...
kirtan403

1
@Tabish,请使用remoteMessage.getNotification()。我们不在这里发送数据。
brijesh kumar,

4

您可以使用Volly Jsonobject请求来完成。

请先执行以下步骤:

1复制旧服务器密钥并将其存储为Legacy_SERVER_KEY

旧版服务器密钥

你可以在图片中看到如何获得

2你需要排球依赖

编译'com.mcxiaoke.volley:library:1.0.19'

在此处输入图片说明

发送推送代码:-

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

是的,您可能需要为其添加标志,并取决于您可以将“推”发送给订阅的用户
Rjz Satvara

@RjzSatvara如果接收方手机未运行该应用程序,该怎么办?它会收到消息吗?在此先感谢
Jaco

1
@Jaco,这是不可能的。您必须通过其他方式进行管理。
Rjz Satvara '17

3

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

3

是的,没有服务器也可以做到。您可以创建设备组客户端,然后在组中交换消息。但是存在局限性:

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

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


您仍然需要一台服务器来发送群组消息
异常

没门。群组中的任何设备都可以发送消息
greywolf82

来自文档:Authorization:key = API_KEY您仍然需要服务器密钥。因此,此解决方案不适用于生产
异常

API密钥是Google帐户,通信仅限于单个用户帐户。请先尝试发表评论。
greywolf82

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

如果您具有要向其发送通知的设备的fcm(gcm)令牌。这只是发送通知的发帖请求。

https://github.com/prashanthd/google-services/blob/master/android/gcm/gcmsender/src/main/java/gcm/play/android/samples/com/gcmsender/GcmSender.java


1
是的,但这仍然需要我们自己的外部服务器对吗?因为我们不应该将API_KEY直接嵌入客户中。我的问题是,如果没有外部服务器,这是否可能实现?目前,其他答复并未提出建议。
Suyash

1

有了Google Cloud Functions,现在无需应用服务器即可在设备之间发送推送通知。

在Google Cloud Functions的相关页面上:

开发人员可以使用Cloud Functions保持用户参与度,并及时了解有关应用程序的相关信息。例如,考虑一个允许用户跟踪应用程序中彼此活动的应用程序。在这样的应用中,由实时数据库写入触发的用于存储新关注者的功能可以创建Firebase Cloud Messaging(FCM)通知,以使适当的用户知道他们已经获得了新关注者。

例:

  1. 该函数在对存储跟随者的实时数据库路径的写入时触发。

  2. 该功能编写一条消息,以通过FCM发送。

  3. FCM将通知消息发送到用户的设备。

这是一个演示项目,用于使用Firebase和Google Cloud Functions发送设备到设备的推送通知。


1

就我而言,我此类Message使用改造

public class Message {

    private String to;
    private String collapseKey;
    private Notification notification;
    private Data data;

    public Message(String to, String collapseKey, Notification notification, Data data) {
        this.to = to;
        this.collapseKey = collapseKey;
        this.notification = notification;
        this.data = data;
    }
}

数据

public class Data {

    private String body;
    private String title;
    private String key1;
    private String key2;

    public Data(String body, String title, String key1, String key2) {
        this.body = body;
        this.title = title;
        this.key1 = key1;
        this.key2 = key2;
    }
}

通知

public class Notification {

    private String body;
    private String title;

    public Notification(String body, String title) {
        this.body = body;
        this.title = title;
    }
}

这个电话

private void sentToNotification() {
    String to = "YOUR_TOKEN";
    String collapseKey = "";
    Notification notification = new Notification("Hello bro", "title23");
    Data data = new Data("Hello2", "title2", "key1", "key2");
    Message notificationTask = new Message(to, collapseKey, notification, data);

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://fcm.googleapis.com/")//url of FCM message server
            .addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
            .build();

    ServiceAPI api = new retrofit.create(ServiceAPI.class);

    Call<Message> call = api .sendMessage("key=YOUR_KEY", notificationTask);

    call.enqueue(new Callback<Message>() {
        @Override
        public void onResponse(Call<Message> call, retrofit2.Response<Message> response) {
            Log.d("TAG", response.body().toString());
        }

        @Override
        public void onFailure(Call<Message> call, Throwable t) {

            Log.e("TAG", t.getMessage());
        }
    });
}

我们的服务

public interface ServiceAPI {
    @POST("/fcm/send")
    Call<Message> sendMessage(@Header("Authorization") String token, @Body Message message);
}

1

您可以使用翻新。将设备订阅主题新闻。从一台设备向另一台设备发送通知。

public void onClick(View view) {

    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
    httpClient.addInterceptor(new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request original = chain.request();

            // Request customization: add request headers
            Request.Builder requestBuilder = original.newBuilder()
                    .header("Authorization", "key=legacy server key from FB console"); // <-- this is the important line
            Request request = requestBuilder.build();
            return chain.proceed(request);
        }
    });

    httpClient.addInterceptor(logging);
    OkHttpClient client = httpClient.build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://fcm.googleapis.com")//url of FCM message server
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
            .build();

    // prepare call in Retrofit 2.0
    FirebaseAPI firebaseAPI = retrofit.create(FirebaseAPI.class);

    //for messaging server
    NotifyData notifydata = new NotifyData("Notification title","Notification body");

    Call<Message> call2 = firebaseAPI.sendMessage(new Message("topic or deviceID", notifydata));

    call2.enqueue(new Callback<Message>() {
        @Override
        public void onResponse(Call<Message> call, Response<Message> response) {

            Log.d("Response ", "onResponse");
            t1.setText("Notification sent");

        }

        @Override
        public void onFailure(Call<Message> call, Throwable t) {
            Log.d("Response ", "onFailure");
            t1.setText("Notification failure");
        }
    });
}

POJO

public class Message {
    String to;
    NotifyData notification;

    public Message(String to, NotifyData notification) {
        this.to = to;
        this.notification = notification;
    }

}

public class NotifyData {
    String title;
    String body;

    public NotifyData(String title, String body ) {

        this.title = title;
        this.body = body;
    }

}

和FirebaseAPI

public interface FirebaseAPI {

    @POST("/fcm/send")
    Call<Message> sendMessage(@Body Message message);

}

0

您可以使用Firebase实时数据库来执行此操作。您可以创建用于存储聊天的数据结构,并为两个用户的会话线程添加观察者。它仍然使用设备-服务器-设备体系结构,但是在这种情况下,开发人员没有其他服务器。这将使用Firebase服务器。您可以在此处查看教程(尽管忽略UI部分,但这也是聊天UI框架的良好起点)。

Firebase实时聊天


3
用户将不会一直使用该应用程序,我们也不能在后台使用firebase实时数据库,因为它与服务器之间保持了持久的套接字连接,而这会严重消耗设备的电量。
Suyash

我可以使用Smack Library在设备和通知之间发送Firebase消息。我没有在Android代码中实现任何外部服务器。Smack使用XMPP协议管理连接和消息节的传入/传出。
i_o

0

所以我在这里有个主意。请参阅:如果FCM和GCM都对http请求有一个点子,我们可以在其中发送包含消息数据的json帖子,包括我们希望发送此消息的设备的令牌。

那么,为什么不将带有此通知的邮件发送到Firebase服务器,以将其发送给用户B?你明白 ?

因此,如果用户在后台使用您的应用程序,则可以发送消息并与呼叫帖子聊天以确保通知的传递。我也很快需要它,以后再测试。你说什么


2
FCM已经有一个端点,请参见此处。但由于它需要服务器api密钥,因此无法直接在我们的客户端中使用它。即使它是公开可访问的,它也会引起安全问题,因为任何用户都可以将任何FCM消息发送给任何人。
Suyash

-3

最简单的方法:

void sendFCMPush(String msg,String token) {
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
    httpClient.addInterceptor(new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request original = chain.request();

            // Request customization: add request headers
            Request.Builder requestBuilder = original.newBuilder()
                    .header("Authorization", "key="+Const.FIREBASE_LEGACY_SERVER_KEY); // <-- this is the important line
            Request request = requestBuilder.build();
            return chain.proceed(request);
        }
    });

    httpClient.addInterceptor(logging);
    OkHttpClient client = httpClient.build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://fcm.googleapis.com/")//url of FCM message server
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
            .build();

    // prepare call in Retrofit 2.0
    FirebaseAPI firebaseAPI = retrofit.create(FirebaseAPI.class);

    //for messaging server
    NotifyData notifydata = new NotifyData("Chatting", msg);

    Call<Message> call2 = firebaseAPI.sendMessage(new Message(token, notifydata));

    call2.enqueue(new Callback<Message>() {
        @Override
        public void onResponse(Call<Message> call, retrofit2.Response<Message> response) {
            Log.e("#@ SUCCES #E$#", response.body().toString());
        }

        @Override
        public void onFailure(Call<Message> call, Throwable t) {

            Log.e("E$ FAILURE E$#", t.getMessage());
        }
    });
}

创建类以创建对象:

public class Message {
String to;
NotifyData data;

public Message(String to, NotifyData data) {
    this.to = to;
    this.data = data;
}
}

创建类以创建对象:

public class Notification {
String title;
String message;
enter code here`enter code here`
public Notification(String title, String message) {
    this.title = title;
    this.message = message;
}
}

在客户端代码中使用Const.FIREBASE_LEGACY_SERVER_KEY是不安全的。发布前,请至少阅读其他回复和评论。
Suyash
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.