离线消息
1. 客户端登录时,服务端不推送全量离线消息,只推送离线消息计数器(数量), 客户端显示在未读小红点
2. 客户端拿到离线消息, 遍历会话列表, 依次将未读消息累加,然后通知服务端清空离线消息计数器的增量数据
3. 客户端进入某会话时,上拉加载, 通过msgId分页查询离线消息
4. 客户端收到消息并保存在本地数据库后,向服务端发送ack, 然后服务端删除离线消息表的离线消息
客户端和服务端消息衔接问题: (客户端本地存储历史消息, 那么客户端下拉加载新消息时,怎么判断是加载本地历史消息,还是请求服务器加载离线消息)
1. 进入会话时, 根据未读消息计数器的最近的N条消息展示首页数据
2. 下载加载时,请求服务端, 服务端按时间倒序返回上一页的消息,只到离线数据库中的数据全部返回给客户端
3. 当离线消息库中没有离线消息时,返回给客户端一个标识, 客户端根据这个标识,在会话页面下一次加载时不请求服务器,直接请求本地数据库
### 本地缓存 客户端消息DB:(简易每个登录用户创建一个DB,切换用户时切换DB即可, 搭建一个完善IM体系 , 每个DB至少对应3张表 ) 1. 一张用户存储聊天列表信息,这里假如它叫ChatList, 用户存储每个群或者单人会话的最后一条信息 。来消息时更新该表,并更新内存数据源中列表信息。或者每次来消息时更新内存数据源中列表信息 ,退出程序或者退出聊天列表页时进行数据库更新。后者避免了频繁操作数据库,效率更高 2. 一张用户存储每个会话中的详细聊天记录 ,这里假如它叫MessageList。该表也是如此 ,要么接到消息立马更新数据库,要么先存入内存中,退出程序时进行数据库缓存 3. 一张用于存储好友或者群列表信息 ,这里假如它叫myFriends ,每次登陆或者退出,或者修改好友备注,删除好友,设置星标好友等操作都需要更新该表
沙盒缓存:
1. 当发送或者接收图片、语音、文件信息时,需要对信息内容进行沙盒缓存。
沙盒缓存的目录分层 ,个人建议是在每个用户根据自己的userID在Cache中创建文件夹,该文件夹目录下创建每个会话的文件夹。
这样做的好处在于 , 当你需要删除聊天列表会话或者清空聊天记录 ,或者app进行内存清理时 ,便于找到该会话的所有缓存。大致的目录结构如下 ../Cache/userID(当前用户ID)/toUserID(某个群或者单聊对象)/...(图片,语音等缓存)
发送消息
1. 文本消息/表情消息
直接调用咱们封装好的ChatManager的sendMessage方法即可 , 发送消息时 ,需要存入或者更新ChatList和MessageList两张表。若是未连接或者发送超时 ,需要重新更新数据库存储的发送成功与否状态 ,同时更新内存数据源 ,刷新该条消息展示即可。
若是表情消息 ,传输过程也是以文本的方式传输 ,比如一个大笑的表情 ,可以定义为[大笑] ,当然规则自己可以和安卓端web端协商,本地根据plist文件和表情包匹配进行图文混排展示即可
- 输入 内容
- 发送
- 消息加入聊天数据源
- 更新数据库
- 展示到聊天会话中
- 调用TCP发送到服务器(若超时,更新聊天数据源,更新数据库 ,刷新聊天UI)
- 收到服务器成功回执(normalReceipt)
- 修改数据源该条消息发送状态(isSend)
- 更新数据库
2. 语音消息
语音需要做相应的降噪 ,压缩等操作 发送语音大约有两种方式 :
-
先对该条语音进行本地缓存 , 然后全部内容均通过TCP传输并携带该条语音的相关信息,例如时长,大小等信息,具体的你得测试一条压缩后的语音体积有多大,若是过大,则需要进行分割然后以消息的方法时发送。接收语音时也进行拼接
-
对该条语音进行本地缓存 , 语音内容使用http传输,传输到服务器生成相应的id ,获取该id再附带该条语音的相关信息 ,以TCP方式发送给对方,当对方收到该条消息时,先去下载该条信息,并根据该条语音的相关信息进行展示。同时发送或接收时,对chatinfo和chatlist表和内存数据源进行更新 ,超时或者失败再次更新
- 长按录制
- 压缩转格式
- 缓存到沙盒
- 更新数据库
- 展示到聊天会话中,展示转圈发送中状态
- 调用http分段式上传(若失败,刷新UI展示)
- 调用TCP发送该语音消息相关信息(若超时,刷新聊天UI)
- 收到服务器成功回执
- 修改数据源该条消息发送状态(isSend)
- 修改数据源该条消息发送状态(isSend)
- 更新数据库
- 刷新聊天会话中该条消息UI
3. 图片消息
图片应当分成两种大小 , 一种是压缩得非常小的状态,一种是图片本身的大小状态。 聊天页面展示的 ,仅仅是小图 ,只有点击查看时才去加载大图。这样做的目的在于提高发送和接收的效率
-
先对该图片进行本地缓存 , 然后全部内容均通过TCP传输 ,并携带该图片的相关信息 ,例如图片的大小 ,名字 ,宽高比等信息 。同样如果过大也需要进行分割传输。同时发送或接收时,对chatinfo和chatlist表和内存数据源进行更新 ,超时或者失败再次更新
-
先对该图片进行本地缓存 , 然后通过http传输到服务器 ,成功后发送TCP消息 ,并携带相关消息 。接收方根据你该条图片信息进行UI布局。同时发送或接收时,对chatinfo和chatlist表和内存数据源进行更新 ,超时或者失败再次更新
打开相册选择图片
获取图片相关信息,大小,名称等,根据用户是否选择原图,考虑是否压缩
缓存到沙盒
更新数据库
展示到聊天会话中,根据上传显示进度
http分段式上传(若失败,更新聊天数据,更新数据库,刷新聊天UI)
调用TCP发送该图片消息相关信息(若超时,更新聊天数据源,更新数据库,刷新聊天UI)
收到服务器成功回执
修改数据源该条消息发送状态(isSend)
更新数据库
刷新聊天会话中该条消息UI
4. 视频消息
建议是走http上传比较好 ,因为内容一般偏大 。TCP部分仅需要传输该视频封面以及相关信息比如时长,下载地址等相关信息即可。接收方可以通过视频大小判断,如果是小视频可以接收到后默认自动下载,自动播放 ,大的视频则只展示封面,只有当用户手动点击时才去加载。具体的还是需要根据项目本身的设计而定
5. 撤回消息
撤回消息也是消息内容的一种类型 。
例如 A给B发送了一条消息 “你好” ,服务端会对该条消息生成一个messageID ,接收方收到该条消息的messageID和发送方的该条消息messageID一致。
如果发送端需要撤回该条消息 ,仅仅需要拿到该条消息messageID ,设置一下消息类型 ,发送给对方
当收到撤回消息的成功回执(repealReceipt)时,移除该会话的内存数据源和更新chatinfo和chatlist表 ,
并加载提示类型的cell进行展示例如“你撤回了一条消息”即可。接收方收到撤回消息时 ,同样移除内存数据源 ,并对数据库进行更新 ,再加载提示类型的cell例如“张三撤回了一条消息”即可
删除消息
消息删除大概分为删除该条消息 ,删除该会话 ,清空聊天记录几种
-
删除该条消息仅仅需要移除本地数据源的消息模型 ,更新chatlist和chatinfo表即可。
-
删除该会话需要移除chatlist和chatinfo该会话对应的列 ,并根据当前登录用户的userID和该会话的toUserID或者groupID移除沙盒中的缓存。
-
清空聊天记录,需要更新chatlist表最后一条消息内容 ,删除chatinfo表,并删除该会话的沙盒缓存.
注意事项
如果服务器推送消息,客户端没有消息回执可能存在丢消息
原因: 服务器对客户端的网络判断不准确。尽管客户端已经和服务端建立了心跳验证 , 但是心跳始终是有间隔的,且TCP的连接中断也是有延迟的。例如,在此时我向服务器发送了一次心跳,然后网络失去了连接,或者网络信号不好。服务器接收到了该心跳 ,服务器认为客户端是处于连接状态的,向我推送了某个人向我发送的消息 ,然而此时我却不能收到消息,所以出现了消息丢失的情况。
解决办法 :客户端向服务端发送消息,服务端会给客户端返回一个回执,告知该条消息已经发送成功。所以,客户端有必要在收到消息时,也向服务端发送一个回执,告知服务端成功收到了该条消息。而客户端,默认收到的所有消息都是离线的,只有收到客户端的接收消息的成功回执后,才会移除掉该离线消息缓存,否则将会把该条消息以离线消息方式同步推送。离线消息后面会做解释。此时的双向回执,可以把消息丢失概率降到非常低 ,基本上算是模拟了一个消息数据传输的三次握手。
消息乱序
原因: 客户端发送消息,该消息会默认赋值当前时间戳 ,收到安卓端或者web端发来的消息时,该时间戳是安卓和web端获取,这样就可能会出现时间戳的误差情况, 当前聊天展示顺序并没有什么问题,因为展示是收到一条展示一条。但是当退出页面重新进入时,如果拉取数据库是根据时间戳的降序拉取 ,那么就很容易出现混乱。
解决办法 : 表结构设置自增ID ,消息的顺序展示以入库顺序为准 ,拉取数据库获取消息记录时,根据自增ID降序拉取 。这样就解决了乱序问题 ,至少保证了,展示的消息顺序和我聊天时的一样。尽管时间戳可能并不一样是按照严谨的降序排列的。