【问题标题】:Chat App using Firebase: Get notification when new message received - Android使用 Firebase 的聊天应用程序:收到新消息时获取通知 - Android
【发布时间】:2017-01-23 21:11:52
【问题描述】:

我正在使用 Firebase 实时数据库开发一个聊天应用。我已经能够正确发送和接收消息。现在,我想在收到新消息时执行通知。为此,我创建了一个Service,它使用ChildEventListener 侦听数据库更改并创建通知。问题是我在onChildAdded 方法中创建通知,并且此方法会为数据库中的现有节点和新节点触发。这导致每当用户从应用程序来回导航时,都会为同一消息创建多次通知。

这是我的实现方式:

chatMsgsRef.orderByChild(FirebaseDBKeys.LOCATION_LAST_UPDATED).addChildEventListener(new ChildEventListener() {

            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {

                ChatMessage message = dataSnapshot.getValue(ChatMessage.class);

                if (!message.getSenderId().equals(currentUserId)) {

                    mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

                    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(NotificationsService.this)
                            .setSmallIcon(R.drawable.message)
                            .setContentTitle("New Message from " + message.getReceipientName())
                            .setContentText(message.getMessage())
                            .setOnlyAlertOnce(true)
                            .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
                    mBuilder.setAutoCancel(true);
                    mBuilder.setLocalOnly(false);


                    mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());

                }
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {

            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });

我如何实现通知在其他聊天应用程序(如 whatsapp 等)中的工作方式??

【问题讨论】:

标签: android firebase notifications firebase-realtime-database


【解决方案1】:

正确的做法是,当有新的聊天发送给客户端时,向客户端推送数据消息。

blog post 解释了如何实现这一点。 基本上,您需要设置一个服务器来监听聊天 firebase ref 并在更新时发送推送通知。这样,您的客户可以在应用内或应用外仍然获得推送。

如果您使用某项服务,则存在许多潜在问题。

首先,您必须保持手机处于唤醒状态。这会耗尽电池电量。

其次,Android 可以随时杀死你的后台服务,所以你的应用可能会突然停止工作。

第三,在打盹模式下,Android 会阻止网络活动并阻止您的应用在后台运行。

【讨论】:

    【解决方案2】:

    我想到了一个更好的答案:

    在这种情况下,您想要的不一定是了解新消息。这是为了知道 UNREAD 消息。

    在 ChatMessage 对象中设置一个“读取”标志(或者相反,在某处有一个值,该值给出最近读取消息的时间戳或 ID)。

    现在,每当 onChildAdded 被触发时,检查是否 read == false。如果是的话,显示消息未读的通知(如果有,记得更新通知,这样只会显示一个通知,会显示最近的一个--哦,还记得在孩子的时候删除通知改为阅读。)

    如果用户在多个设备上使用您的应用,它会正确检查读取状态。如果他在手机上阅读了最新消息,然后转到平板电脑,它将不会显示新消息通知。

    如果您愿意,您甚至可以使用此功能来指示收件人已将您的消息读给他们。

    你怎么知道它什么时候被读取?也许只是当您将它添加到屏幕上时。也许您确保它在视图中(未滚动到屏幕外)并且可见几秒钟。

    【讨论】:

    • 乍得你能给我看一个例子或这种情况的实际实现吗?
    【解决方案3】:

    你试过 addValueEventListener 吗?

    https://www.firebase.com/docs/android/guide/retrieving-data.html#section-start

    // Get a reference to our posts
    Firebase ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");
    // Attach an listener to read the data at our posts reference
    ref.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot snapshot) {
            System.out.println(snapshot.getValue());
        }
        @Override
        public void onCancelled(FirebaseError firebaseError) {
            System.out.println("The read failed: " + firebaseError.getMessage());
        }
    });
    

    “只要将新数据添加到我们的 Firebase 引用中,就会调用此方法,我们不需要编写任何额外的代码来实现这一点。”

    编辑: “它会随着初始数据触发一次,每次数据更改时都会触发一次。” 我还没有尝试过,但我的想法是在方法第一次触发时设置一个标志。然后,只要设置了方法和标志(换句话说,第二次、第三次、第四次等),就从快照中获取最新的对象。

    【讨论】:

    • 我可以试试。但是我将如何仅获取使用addValueEventListener 添加的新数据??
    • 基本上,我正在做的是我使用用户的手机号码作为标识符和push它下的所有新消息,然后我将整个节点同步到listview
    • 这是一种“糟糕”的做法,原因有很多(电池耗尽、如果有很多新消息时会发出多个通知等),但至少它“有效”
    【解决方案4】:

    @Chad Shultz 的答案与您的原始代码相结合,并添加了一些数据库结构。

    您将按照最初的意图使用查询

    chatMsgsRef.orderByChild(...)
    

    但是,将数据结构更改为具有以下布局

    >root
        > users
            > userID_001
                > chats
                    > chat_001:true
            > userID_002
                > chats
                    > chat_001:true
        > chats
            > chat_001
                > participants
                    > userID_001:true
                    > userID_002:true
                > messages
                    > msg_001
                        > sender:"userID_001"
                        > text:"some message"
                        > time:"some time"
        > message_read_states
            > chat_001
                > msg_001
                    > userID_001:true
                    > userID_002:false
    

    因此,无论何时发送消息,它都会被推送到“消息”节点。 接下来,系统获取所有“参与者”,并将它们推送到该聊天/消息的“message_read_states”节点下,除了发送者之外,每个人的值为 false。 当有人阅读消息时,他们在那里将其值更改为 true。

    现在服务需要确定通知用户哪些消息。 服务将在 message_read_states/chat_X 上放置一个侦听器,并按子字段“userID_X”的值对其进行排序(取决于当前用户是谁)。 我们将使查询只返回每个聊天的消息 ID,在它下面有一个值“userID_X:false”

    因此,在本例中,对于“userID_002”,查询将返回“msg_001”,但对于“userID_001”,它不会返回任何内容,因为找不到该键所需的值(他/她是发件人)。

    查询将在您的 Service 的 onStartCommand 中按如下方式构造:

        //TODO: Get the ID of the chat the user is taking part in
        String chatID = "chat_001";
    
        // Check for new messages
        FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
        if (currentUser != null){
            String UID = currentUser.getUid();
            DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
            Query query = rootRef.child("message_read_states").child(chatID).orderByChild(UID).equalTo(false);
            query.addChildEventListener(new ChildEventListener() {
                @Override
                public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                    String messageID = dataSnapshot.getKey();
                    //TODO: Handle Notification here, using the messageID
                    // A datasnapshot received here will be a new message that the user has not read
                    // If you want to display data about the message or chat,
                    // Use the chatID and/or messageID and declare a new 
                    // SingleValueEventListener here, and add it to the chat/message DatabaseReference.
                }
    
                @Override
                public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                    String messageID = dataSnapshot.getKey();
                    //TODO: Remove the notification
                    // If the user reads the message in the app, before checking the notification
                    // then the notification is no longer relevant, remove it here.
                    // In onChildAdded you could use the messageID(s) to keep track of the notifications
                }
    
                @Override
                public void onChildRemoved(DataSnapshot dataSnapshot) {
    
                }
    
                @Override
                public void onChildMoved(DataSnapshot dataSnapshot, String s) {
    
                }
    
                @Override
                public void onCancelled(DatabaseError databaseError) {
    
                }
            });
        }
    

    当然,现在用户很可能会参与多个聊天。您必须遍历与用户关联的所有聊天,并为每个聊天实现一个查询。

    来源:我目前正在开发一个带有聊天系统的 Firebase 应用

    【讨论】:

      【解决方案5】:

      您可以免费且无需任何服务!

      Shmuel 所说的是正确的做法。但是,要执行他链接的博客文章中描述的内容,您需要云功能。而且它们在 Firebase 上不是免费的,您必须付费。

      其他人在他们的答案中描述的东西是有效的,但它并不好,因为它不可靠(例如,如果用户重新启动他的手机它将无法工作)并且这样的后台服务会大大降低设备的速度。


      解决方案:

      您几乎可以在用户后端执行这些云功能中的操作。

      这也不完美,因为您需要在代码中存储敏感信息。因此,恶意用户可以获取信息并滥用它来向您的用户发送通知。

      但它是免费的、可靠的,而且根本不会减慢设备速度。

      下面是实现。


      模块:应用程序依赖项

      implementation 'com.squareup.retrofit2:retrofit:2.6.0'
      implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
      

      ApiClient

      public class ApiClient {
      
          private static final String BASE_URL = "https://fcm.googleapis.com/";
          private static Retrofit retrofit = null;
      
          public static Retrofit getClient() {
              if (retrofit == null) {
                  retrofit = new Retrofit.Builder()
                          .baseUrl(BASE_URL)
                          .addConverterFactory(GsonConverterFactory.create())
                          .build();
              }
              return retrofit;
          }
      }
      

      API接口

      public interface ApiInterface {
      
          @Headers({"Authorization: key=" + ConstantKey.SERVER_KEY, "Content-Type:application/json"})
          @POST("fcm/send")
          Call<ResponseBody> sendNotification(@Body RootModel root);
      }
      

      根模型

      public class RootModel {
      
          @SerializedName("to") //  "to" changed to token
          private String token;
      
          @SerializedName("notification")
          private NotificationModel notification;
      
          @SerializedName("data")
          private DataModel data;
      
          public RootModel(String token, NotificationModel notification, DataModel data) {
              this.token = token;
              this.notification = notification;
              this.data = data;
          }
      
          public String getToken() {
              return token;
          }
      
          public void setToken(String token) {
              this.token = token;
          }
      
          public NotificationModel getNotification() {
              return notification;
          }
      
          public void setNotification(NotificationModel notification) {
              this.notification = notification;
          }
      
          public DataModel getData() {
              return data;
          }
      
          public void setData(DataModel data) {
              this.data = data;
          }
      }
      

      通知模型

      public class NotificationModel {
      
          private String title;
          private String body;
      
          public NotificationModel(String title, String body) {
              this.title = title;
              this.body = body;
          }
      
          public String getBody() {
              return body;
          }
      
          public void setBody(String body) {
              this.body = body;
          }
      
          public String getTitle() {
              return title;
          }
      
          public void setTitle(String title) {
              this.title = title;
          }
      
      }
      

      数据模型

      public class DataModel {
      
          private String name;
          private String age;
      
          public DataModel(String name, String age) {
              this.name = name;
              this.age = age;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getAge() {
              return age;
          }
      
          public void setAge(String age) {
              this.age = age;
          }
      
      }
      

      使用此方法发送通知

      private void sendNotificationToUser(String token) {
          RootModel rootModel = new RootModel(token, new NotificationModel("Title", "Body"), new DataModel("Name", "30"));
      
          ApiInterface apiService =  ApiClient.getClient().create(ApiInterface.class);
          retrofit2.Call<ResponseBody> responseBodyCall = apiService.sendNotification(rootModel);
      
          responseBodyCall.enqueue(new Callback<ResponseBody>() {
              @Override
              public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
                  Log.d(TAG,"Successfully notification send by using retrofit.");
              }
      
              @Override
              public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) {
      
              }
          });
      }
      

      您可以在“消息”选项卡的 Firebase 项目设置中获取 ConstantKey.SERVER_KEY

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-19
        • 1970-01-01
        • 2020-04-17
        • 1970-01-01
        • 2018-11-14
        相关资源
        最近更新 更多