开发简单Android聊天软件(7)
构建离线消息获取流程
在 “开发简单Android聊天软件(6)” 中,完成了完成消息接收和加载,构建一个完整的聊天流程。
但是我们只完成了一半,完成存量历史记录展示,和即时聊天的接受处理,和页面的实时刷新。目前我们来讲关于聊天记录的另一部分,离线消息数据获取。
一、服务端改造,存储离线聊天数据
1、首先我们要弄清楚一点:什么是在线?什么是离线?在线离线在程序中,实际应该由socket长链接作为代表。手机端和服务器的长链接挂上了,说明用户在线。长链接断开了,说明用户离线了。
手机端和服务端的socket就在这个时候是核心作用。回忆一下,在开发简单Android聊天软件(2)中,关于服务端长链接的ChatHandler类。
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {public static HashMap<ChannelHandlerContext,String> client=new HashMap<String, ChannelHandlerContext>();@Overridepublic void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {System.out.println(msg.text());if(msg.text().substring(0,1).equals("u")){ //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+" u_*** "+client.size());}else {System.out.println("收到消息"+msg.text());if(client.get(ctx) != null){ //判断消息是否来自于已经绑定上线的用户,判断通过hashmap查询user_id不能为空Gson gson = new Gson();Im_msg_content im_msg_content = gson.fromJson(String.valueOf(msg.text()), Im_msg_content.class); //收到的是聊天消息String recipient_id = im_msg_content.getRecipient_id; //获取消息接收方user_id:u_00002ChannelHandlerContext recipient_ctx;for(ChannelHandlerContext getCtx:client.keySet()){ //hashmap通过userid 获取ctxif(client.get(getCtx).equals(recipient_id)){recipient_ctx= getCtx; //拿到u_00002的ctxsendMessage(recipient_ctx,msg.text()) //调用sendMesage方法,发送消息}}}else{ System.out.println("未绑定用户id的长链接发出的消息,不予处理:"+msg.text());}}}//客户端连接@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception { }//客户端断开@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {client.remove(ctx)}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {e.printStackTrace();}//给指定链接发消息public void SendMessage(ChannelHandlerContext ctx String message) {ctx.channel().writeAndFlush(new TextWebSocketFrame(message));}}
梳理一下核心逻辑。手机端长链接与服务器接上的那一刻,是handlerAdded()方法触发执行了。
然后手机端根据我们之前写好的逻辑,会主动发送一条,文本为自己用户id的消息,此时channelRead0()触发执行。
最后手机端关闭app,长链接断掉,此时handlerRemoved()触发执行。
理明白后,我们就可以过长链接的方法,来给长链接做一个简单的记录。也就是代码中定义的HashMap<ChannelHandlerContext,String> client
我们将在这个hashmap中,把“记录长链接ctx”和“删除长链接ctx”的两个动作,视为用户app的在线和离线。所以我们在channelRead0()中,识别到app端,用userid发消息,视为用户长链接挂载成功。给定义的hashmap添加ctx记录。为了方便知道是哪个用户上线,所以key为ctx,value为userid,行程对应关系。
if(msg.text().substring(0,1).equals("u")){ //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+" u_*** "+client.size());}else{System.out.println("收到消息"+msg.text());....}
同样的,我们也在handlerRemoved(),用户下线的地方,调用remove,清除长链接记录。在后续判断中,我们只需要判断用户userid是否存在与map中,则可以判断APP端是否在线。
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {client.remove(ctx)
}
2、接下来,开始做离线消息存储。逻辑大概分两步:1、判断消息属于离线消息,2、保存离线消息。
逻辑在 client.get(getCtx).equals(recipient_id)) 判断的 else 部分。
if(client.get(ctx) != null){ //判断消息是否来自于已经绑定上线的用户,判断通过hashmap查询user_id不能为空Gson gson = new Gson();Im_msg_content im_msg_content = gson.fromJson(String.valueOf(msg.text()), Im_msg_content.class); //收到的是聊天消息String recipient_id = im_msg_content.getRecipient_id; //获取消息接收方user_id:u_00002ChannelHandlerContext recipient_ctx;for(ChannelHandlerContext getCtx:client.keySet()){ //hashmap通过userid 获取ctxif(client.get(getCtx).equals(recipient_id)){recipient_ctx= getCtx; //拿到u_00002的ctxsendMessage(recipient_ctx,msg.text()) //调用sendMesage方法,发送消息}else{ //在hashmap中找不到接收用户recipient_id:u_00002所需要的ctx,则判断用户u_00002未登录app端,为离线状态addOfflineMsg(im_msg_content); //存储离线消息//此处使用addOfflineMsg()的方法,对应方法实现通过jdbc调用insert ,将数据放入mysql数据库。//其中涉及springboot项目配置使用mysql,数据库操作dao类等。//全部展开的话与本节主题偏离甚远,所以本次就略过了。//如果后续大家反馈需要的话,我就在后面章节补充此部分代码和逻辑}}}else{ System.out.println("未绑定用户id的长链接发出的消息,不予处理:"+msg.text());}
至此,发离线消息部分就全部完成了,后面是接受用户,从离线恢复到上线,其离线期间,消息的接收逻。辑
3、离线消息的接收,也是涉及服务端的改造。让我们回到 channelRead0() 中长链接判断用户上线的逻辑。
if(msg.text().substring(0,1).equals("u")){ //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+" u_*** "+client.size());}
之前,我们通过用户发的消息是否是u开头的用户名,作为用户在app端上线的标志。
这样判断的原因,之前我们也明确过:因为我们在android侧,将长链接挂上之后,会立马执行发送用户id的文本。所以是可以通过这个判断的。
我们的离线消息获取的逻辑,也将会写在这里。
if(msg.text().substring(0,1).equals("u")){ //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+" u_*** "+client.size());//在此继续List<Im_msg_content> offlinelist = new ArrayList<>();//queryOfflineContentByRecipientId: 通过userid搜索属于该用户的离线消息//该逻辑也是涉及mysql,查询方法,实现逻辑略过offlinelist = queryOfflineContentByRecipientId(msg.text().toString()); if (offlinelist.size() > 0) { //若存在离线消息记录System.out.println(user_id + " 有" + offlinelist.size() + "条离线消息");for (Im_msg_content i : offlinelist) {int mid = i.getMid();sendMessage(ctx, new Gson().toJson(i)); //引用Gson序列化,将对象转为json格式,发送//deleteOfflineByMid: mysql操作,通过where mid = ? 条件,将发送成功的离线数据进行删除deleteOfflineByMid(mid);}} else {System.out.println(user_id + " 无离线消息");}}
这样改造完成之后,离线消息的接收就完成实现了。
(1)用户启动android app
(2)android socket自动触发连接成功
(3)socket 默认发出第一条消息:userid
(4)服务端收到 userid,判断用户登录成功。随后进行查询该用户的离线记录。然后通过长链接依次向android端发送mysql存下的离线消息。
(5)android通过长链接接收方法,收到离线消息,进行存储,同时刷新消息窗口页面
三、总结
本次我们也完成了两个部分的内容:
1、用户B不在线时,此期间收到的消息,都会进行存储。(理论上,无论是否在线,服务端都应该存储消息,供用户远程同步)
2、用户B上线后,查询离线期间,所有离线消息,然后发送到手机端,手机端通过“开发简单Android聊天软件(6)”中的逻辑进行刷新。
关于其中操作mysql数据库的那三个方法,我没有展开。
springboot如何操作数据库,网上教程应该不少。我把大致需要的表结构放在下面,大家可以各显神通,自行实现。
DROP DATABASE IF EXISTS `app`;
CREATE DATABASE IF NOT EXISTS `app`
USE `app`;/*用户表 */
DROP TABLE IF EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user` (`id` int(11) NOT NULL,`user_id` varchar(50) NOT NULL,`user_nickname` varchar(50) NOT NULL,`user_password` varchar(50) NOT NULL,`user_name` varchar(50) NOT NULL,`user_mail` varchar(50) NOT NULL,`user_phone` varchar(50) DEFAULT NULL,`user_token` varchar(50) DEFAULT NULL,`status` varchar(50) NOT NULL COMMENT '0:管理员,1:注册用户'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*离线消息表 */
DROP TABLE IF EXISTS `im_msg_content_offline`;
CREATE TABLE IF NOT EXISTS `im_msg_content_offline` (`mid` int(11) NOT NULL COMMENT '表主键',`cid` varchar(50) NOT NULL COMMENT '会话id',`id` int(11) NOT NULL COMMENT '个人会话id',`content` varchar(1000) NOT NULL,`sender_id` varchar(50) NOT NULL,`recipient_id` varchar(50) NOT NULL,`msg_type` int(11) NOT NULL,`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`mid`,`cid`),INDEX `recipient_id` (`recipient_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*消息记录表 */
/*全量消息记录,用于用户远程同步聊天记录,1000w数据量以下无需分表 */
DROP TABLE IF EXISTS `im_msg_content`;
CREATE TABLE IF NOT EXISTS `im_msg_content` (`mid` int(11) NOT NULL COMMENT '表主键',`cid` varchar(50) NOT NULL COMMENT '会话id',`id` int(11) NOT NULL COMMENT '个人会话id',`content` varchar(1000) NOT NULL,`sender_id` varchar(50) NOT NULL,`recipient_id` varchar(50) NOT NULL,`msg_type` int(11) NOT NULL,`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`mid`,`cid`)INDEX `create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
开发简单Android聊天软件(7)相关推荐
- 开发简单Android聊天软件(6)
构建完整消息接收加载流程 在 "开发简单Android聊天软件(5)" 中,完成了会话窗口的绘制,以及消息发送.现在我们来完成消息接收和加载,构建一个完整的聊天流程. 消息加载,那 ...
- 开发简单Android聊天软件(1)
总体介绍 开篇 大概思路 一. 客户端主要依赖 二.包引用完成后,创建wsClient类. 三.连接成功后,就可以在对于业务逻辑调用以下方法开始发送消息 开篇 本人是一位开发新人,将自己的开发学习过程 ...
- 开发简单Android聊天软件(2)
服务端实现大概思路 服务端需要创建socket端口供客户端(android)链接,为了能实现消息的转发,还需要记录一下长链接与用户的对应关系.总体大概思路是,服务端端口创建完成上线后,每次收到客户端的 ...
- Android聊天软件的开发(四)--通讯录
一,好友排序 好友排序是按照昵称拼音进行A-Z排序.效果如下图: 对好友昵称进行排序,需要先将首字转换为ASCII码,然后根据ASCII码得到对应的拼音,最后根据拼音进行A-Z排序.点 ...
- Android聊天软件的开发(六)--表情
表情用于聊天对话的输入,实现的原理主要是:在EditText或TextView中,使用SpannableString,将特定字符串替换为图片. 首先,我们可以规定,表情的字符串为[**],图片名称为s ...
- Android聊天软件的开发(七)--聊天通信
聊天通信通过Socket实现,大概的框架如下图: 通信流程: 1.服务器在启动时开启聊天服务线程 可以通过ServletContextListener监听Servlet的初始化和销毁,来开启和关闭聊天 ...
- Android聊天软件的开发--聊天通信
Android聊天软件的开发(七)--聊天通信 2014-06-20 23:17:49CSDN-vaintwyt-点击数:338 聊天通信通过Socket实现,大概的框架如下图: 通信流程: 1.服 ...
- Android聊天软件的开发(二)--数据库
一,服务器数据库 服务器端的数据库是MySQL,使用Hibernate实现数据的增删改查.主要存储的数据有:用户信息,好友列表. 其中,好友列表中的friend_list ...
- Android聊天软件的开发(三)--网络连接
一,服务器网络接口 服务器网络接口通过Servlet实现,可以获得客户端提交的数据,对数据进行查询存储操作,以及返回结果数据给客户端.客户端可以通过HTTP协议直接访问网络接口. HTTP ...
最新文章
- 一看就懂!【英雄联盟锐雯】与 Python 详解设计模式之门面模式
- React入门看这篇就够了
- 数组的最后一位的下一位为什么是0?
- php xml 添加节点 出问题,PHP往XML中添加节点的方法
- 关于如何安装cocoapods
- python自动化输入文本_快速掌握Python Selenium Web自动化:)四、使用Selenium在网页上进行操作...
- fiddler4安装教程以及手机下载证书时报错 no root certificate was found解决方法
- React Native集成阿里云推送----广播推送
- java 共享类,Java 技术,IBM 风格: 类共享
- webSpider----request
- 联想z5 android pie,联想z5成功吃上安卓pie
- [Linux](小白须知)超级用户、普通用户、系统管理员怎么区分
- 2020新冠疫情下如何防范恶意软件攻击避免数据泄露?
- ping6 之 unknown host 解决方法
- 各种界面库介绍及比较
- 焦深(depth of focus)
- The 19th Zhejiang Provincial Collegiate Programming Contest
- 【综述】NL2SQL (二) WikiSQL
- 谷歌浏览器中文乱码问题
- Jedis 使用详解(官方原版)
热门文章
- 不看公式自己复原魔方
- 黑龙江第三方软件测试机构 CMA/CNAS双资质
- 过滤链FilterChain,请求与响应--chain.doFilter()代码前后
- oracle函数怎么遍历二维数组,oracle存储过程之游标查询,一维和二维数组,循环,条件判断例子...
- 龙格现象及分段线性插值
- 第3章--Linux内核及内核编程
- 动态链接库和静态链接库
- MSAA,SSAA,CSAA,CFAA...如何正确地为您的游戏设置抗锯齿模式
- TCAM CAM 说明 原理 结构 Verilog 硬件实现
- ElementUI tree超出显示省略号