使用 Firebase 的聊天应用程序:收到新消息时获取通知 - Android

Posted

技术标签:

【中文标题】使用 Firebase 的聊天应用程序:收到新消息时获取通知 - Android【英文标题】:Chat App using Firebase: Get notification when new message received - 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 等)中的工作方式??

【问题讨论】:

你考虑过使用firebase-cloud-messaging吗? Whatsapp 技术完全不同。它运行在客户端聊天存储codementor.io/vigneshwaranb/… 【参考方案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

【讨论】:

以上是关于使用 Firebase 的聊天应用程序:收到新消息时获取通知 - Android的主要内容,如果未能解决你的问题,请参考以下文章

android firebase 一对一聊天应用

多个流到 Firebase 文档而不进行处理

使用 Firebase 函数向 Android 应用发送通知

通过 Firebase 函数发送的 Swift 中的加密推送通知

添加新消息时聊天应用程序不滚动

在 StreamBuilder 中使用 AnimatedList