最近项目的聊天模块中增加了一个消息已读回执的功能,从技术上不是很难实现,但还是在这里记录一下,以便以后查阅。
所谓的消息已读回执,就是双方聊天时,如果对方看到了你新发的信息,这条消息在你这端就会标为“已读”,否则将是“未读”。
A发送一条消息给B,B接收了,如果B此时打开聊天主界面,查看了消息,B就再发一条“回执”(携带有B已读的消息Id)给A,A这时根据“回执”中的消息Id,标注已读。
那么根据整个功能流程,首先,我们要对消息model进行一下改造,增加一个状态位的属性toReadState,来区分消息是否被对方读了;还需要增加一个属性toReadMessageId,这个属性主要用在“回执”当中,用来告诉对方,我读了你的哪条消息,以便对方标注。
public int toReadStatus;
public String toReadMessageId;
这样改造过后,如果消息带有toReadMessageId,那么它就不是一条消息,而是一条“回执”,而“回执”是不需要显示在会话列表或聊天面板中的,因此,我们需要在接收消息的时候判断一下,如果是“回执”,则不显示也不存储到数据库中。
接收消息主要有两个场景:1.会话列表 2.聊天主界面
如图:
因此在发送回执的时候也要根据场景分成两种情况,在会话列表中,我们肯定不能将消息状态置为已读,只有打开聊天主界面,并且此条消息在屏幕中时,才可以发送回执。因此,在我们打开主界面时,我们要判断一下有哪些消息是处于未读状态并且是处于当前屏幕上的,将这些状态的回执发给对方:
/**
* 批量发送回执给对方并更新界面和数据库
* @param firstVisibleItem 屏幕中第一条可见消息
*/
private void sendToReadMessage(int firstVisibleItem) {
if (messageInfos != null && messageInfos.size() != 0) {
SQLiteDatabase db = DBHelper.getInstance(mContext).getReadableDatabase();
MessageTable messageTable = new MessageTable(db);
for (int i = firstVisibleItem; i < messageInfos.size(); i++) {
MessageInfo messageInfo = messageInfos.get(i);
// 状态位为0,并且不能是自己发的消息
if (messageInfo.toReadState == 0 && !messageInfo.fromid.equals(ResearchCommon.getUserId(mContext))) {
messageInfo.toReadState = 1;
//发送回执
sendReadState(messageInfo.id);
//保存到数据库中
messageTable.update(messageInfo);
//更新显示
mAdapter.notifyDataSetChanged();
}
}
}
}
/**
* 发送回执给对方
* @param id 已读的MessageId
*/
private void sendReadStatus(String id) {
send();
MessageInfo msg = new MessageInfo();
msg.fromid = ResearchCommon.getUserId(mContext);
msg.tag = UUID.randomUUID().toString();
msg.fromname = mLogin.nickname;
msg.fromurl = mLogin.headsmall;
msg.toid = fCustomerVo.uid;
msg.toname = getFromName();
msg.tourl = fCustomerVo.headsmall;
msg.typefile = MessageType.TEXT;
msg.content = "";
msg.typechat = mType;
msg.time = System.currentTimeMillis();
msg.readState = 1;
//告诉对方已读哪条信息
msg.toReadMessageId = id;
sendBroad2Save(msg, false, true);
}
在聊天主界面中发送回执则比较简单:
// 接受消息
private void notifyMessage(final MessageInfo msg) {
if (msg == null) {
return;
}
handler.post(new Runnable() {
@Override
public void run() {
try {
// 当该信息不来自好友就过滤掉!
if (msg.getFromId().equals(ResearchCommon.getUserId(mContext))) {
return;
}
//如果不是回执
if (msg.toReadMessageId.equals("-1")) {
messageInfos.add(msg);
//更新头像
updateUserHead(msg);
mAdapter.notifyDataSetInvalidated();
if (messageInfos.size() == 1
|| (mListView.getLastVisiblePosition() == messageInfos.size() - 2)) {
mListView.setSelection(messageInfos.size() - 1);
}
//发送已读回执
sendReadStatus(msg.id);
startDeleteTimer(msg);
} else {
//如果是回执的话就根据id更新已读状态
updateReadState(msg.toReadMessageId);
}
} catch (Exception e) {
}
}
});
}
private void updateReadState(String id) {
if (mType == GlobleType.SINGLE_CHAT) {
for (int i = 0; i < messageInfos.size(); i++) {
final MessageInfo messageInfo = messageInfos.get(i);
if (id.equals(messageInfo.id)) {
messageInfo.toReadStatus = 1;
SQLiteDatabase db = DBHelper.getInstance(mContext).getReadableDatabase();
MessageTable messageTable = new MessageTable(db);
messageTable.update(messageInfo);
mAdapter.notifyDataSetInvalidated();
startDeleteTimer(messageInfo);
}
}
}
}
还有一点需要注意的是,在监听新消息广播的时候也要判断一下该消息的类型,如果是回执类型就不要存储或在系统通知栏进行提示。