使用 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的主要内容,如果未能解决你的问题,请参考以下文章
使用 Firebase 函数向 Android 应用发送通知