本文由作者FreddyChen原创分享,为了更好的体现文章价值,即时通讯网引用时有少许改动,感谢原作者。
WX20190721-165501@2x.jpg (5.75 KB, 下载次数: 1955)
下载附件 保存到相册
5 年前 上传
1.jpg (14.43 KB, 下载次数: 1962)
7-1.jpg (32.1 KB, 下载次数: 1942)
7-2.jpg (96.52 KB, 下载次数: 1914)
syntax = "proto3";// 指定protobuf版本 option java_package = "com.freddy.im.protobuf";// 指定包名 option java_outer_classname = "MessageProtobuf";// 指定生成的类名 message Msg { Head head = 1;// 消息头 string body = 2;// 消息体 } message Head { string msgId = 1;// 消息id int32 msgType = 2;// 消息类型 int32 msgContentType = 3;// 消息内容类型 string fromId = 4;// 消息发送者id string toId = 5;// 消息接收者id int64 timestamp = 6;// 消息时间戳 int32 statusReport = 7;// 状态报告 string extend = 8;// 扩展字段,以key/value形式存放的json }
7-3.jpg (19.19 KB, 下载次数: 1921)
7-4.jpg (15.66 KB, 下载次数: 1934)
7-5.jpg (41.05 KB, 下载次数: 1927)
7-6.jpg (45.06 KB, 下载次数: 2033)
7-7.jpg (51.32 KB, 下载次数: 1873)
8-1.jpg (65.07 KB, 下载次数: 1987)
8-2.jpg (49.79 KB, 下载次数: 1937)
8-3.jpg (43.71 KB, 下载次数: 1846)
8-4.jpg (44.53 KB, 下载次数: 1986)
8-5.jpg (46.02 KB, 下载次数: 1964)
8-6.jpg (42.8 KB, 下载次数: 2062)
8-7.jpg (41.97 KB, 下载次数: 1988)
9-1.jpg (55.81 KB, 下载次数: 1952)
9-2.jpg (37.83 KB, 下载次数: 2070)
9-3.jpg (41.44 KB, 下载次数: 1943)
9-4.jpg (27.97 KB, 下载次数: 1973)
9-5.jpg (18.83 KB, 下载次数: 1980)
10-1.jpg (42.44 KB, 下载次数: 2060)
10-2.jpg (45.66 KB, 下载次数: 1953)
10-3.jpg (28.76 KB, 下载次数: 1942)
10-4.jpg (54.83 KB, 下载次数: 1880)
10-5.jpg (15.29 KB, 下载次数: 1870)
10-6.jpg (29.05 KB, 下载次数: 1991)
10-7.jpg (57.47 KB, 下载次数: 1981)
11-1.jpg (24.91 KB, 下载次数: 1885)
12-1.jpg (68.96 KB, 下载次数: 1946)
12-2.jpg (53.96 KB, 下载次数: 1920)
13-1.jpg (70.95 KB, 下载次数: 1949)
13-2.jpg (50.88 KB, 下载次数: 1914)
13-3.jpg (52.51 KB, 下载次数: 1979)
13-4.jpg (70.52 KB, 下载次数: 1960)
13-5.jpg (74.8 KB, 下载次数: 1916)
13-6.jpg (34.86 KB, 下载次数: 1978)
13-7.jpg (34.68 KB, 下载次数: 2049)
13-8.jpg (62.66 KB, 下载次数: 1975)
13-9.jpg (78.25 KB, 下载次数: 1945)
13-10.jpg (83.21 KB, 下载次数: 1915)
13-11.jpg (86.87 KB, 下载次数: 1967)
13-12.jpg (31.98 KB, 下载次数: 1939)
14-1.gif (28.6 KB, 下载次数: 1973)
14-2.gif (107.19 KB, 下载次数: 1926)
15-1.jpg (56.41 KB, 下载次数: 1875)
15-2.jpg (45.96 KB, 下载次数: 1895)
15-3.jpg (50.81 KB, 下载次数: 1940)
15-4.jpg (40.19 KB, 下载次数: 1974)
15-5.jpg (61.34 KB, 下载次数: 1825)
15-6.jpg (54.61 KB, 下载次数: 1906)
15-7.jpg (85.28 KB, 下载次数: 1867)
服务端接收到客户端A发送给客户端B的消息后,先给客户端A回复一条状态报告,告诉客户端A,我已经收到消息,这个时候,客户端A就不用管了,消息只要到达服务端即可。然后,服务端先尝试把消息转发到客户端B,如果这个时候客户端B收到服务端转发过来的消息,需要立马给服务端回一条状态报告,告诉服务端,我已经收到消息,服务端在收到客户端B返回的消息接收状态报告后,即认为此消息已经正常发送,不需要再存库。 如果客户端B不在线,服务端在做转发的时候,并没有收到客户端B返回的消息接收状态报告,那么,这条消息就应该存到数据库,直到客户端B上线后,也就是长连接建立成功后,客户端B主动向服务端发送一条离线消息询问,服务端在收到离线消息询问后,到数据库或缓存去查客户端B的所有离线消息,并分批次返回,客户端B在收到服务端的离线消息返回后,取出消息id(若有多条就取id集合),通过离线消息应答把消息id返回到服务端,服务端收到后,根据消息id从数据库把对应的消息删除即可。
16-1.jpg (67.82 KB, 下载次数: 1830)
package com.freddy.chat.im; import android.util.Log; import com.freddy.chat.bean.AppMessage; import com.freddy.chat.bean.BaseMessage; import com.freddy.chat.bean.ContentMessage; import com.freddy.chat.im.handler.IMessageHandler; import com.freddy.chat.im.handler.MessageHandlerFactory; import com.freddy.chat.utils.CThreadPoolExecutor; /** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: MessageProcessor.java</p> * <p>@PackageName: com.freddy.chat.im</p> * <b> * <p>@Description: 消息处理器</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/10 03:27</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public class MessageProcessor implements IMessageProcessor { private static final String TAG = MessageProcessor.class.getSimpleName(); private MessageProcessor() { } private static class MessageProcessorInstance { private static final IMessageProcessor INSTANCE = new MessageProcessor(); } public static IMessageProcessor getInstance() { return MessageProcessorInstance.INSTANCE; } /** * 接收消息 * @param message */ @Override public void receiveMsg(final AppMessage message) { CThreadPoolExecutor.runInBackground(new Runnable() { @Override public void run() { try { IMessageHandler messageHandler = MessageHandlerFactory.getHandlerByMsgType(message.getHead().getMsgType()); if (messageHandler != null) { messageHandler.execute(message); } else { Log.e(TAG, "未找到消息处理handler,msgType=" + message.getHead().getMsgType()); } } catch (Exception e) { Log.e(TAG, "消息处理出错,reason=" + e.getMessage()); } } }); } /** * 发送消息 * * @param message */ @Override public void sendMsg(final AppMessage message) { CThreadPoolExecutor.runInBackground(new Runnable() { @Override public void run() { boolean isActive = IMSClientBootstrap.getInstance().isActive(); if (isActive) { IMSClientBootstrap.getInstance().sendMessage(MessageBuilder.getProtoBufMessageBuilderByAppMessage(message).build()); } else { Log.e(TAG, "发送消息失败"); } } }); } /** * 发送消息 * * @param message */ @Override public void sendMsg(ContentMessage message) { this.sendMsg(MessageBuilder.buildAppMessage(message)); } /** * 发送消息 * * @param message */ @Override public void sendMsg(BaseMessage message) { this.sendMsg(MessageBuilder.buildAppMessage(message)); } }
/** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: IMSEventListener.java</p> * <p>@PackageName: com.freddy.chat.im</p> * <b> * <p>@Description: 与ims交互的listener</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/07 23:55</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public class IMSEventListener implements OnEventListener { private String userId; private String token; public IMSEventListener(String userId, String token) { this.userId = userId; this.token = token; } /** * 接收ims转发过来的消息 * * @param msg */ @Override public void dispatchMsg(MessageProtobuf.Msg msg) { MessageProcessor.getInstance().receiveMsg(MessageBuilder.getMessageByProtobuf(msg)); } /** * 网络是否可用 * * @return */ @Override public boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) NettyChatApp.sharedInstance().getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = cm.getActiveNetworkInfo(); return info != null && info.isConnected(); } /** * 设置ims重连间隔时长,0表示默认使用ims的值 * * @return */ @Override public int getReconnectInterval() { return 0; } /** * 设置ims连接超时时长,0表示默认使用ims的值 * * @return */ @Override public int getConnectTimeout() { return 0; } /** * 设置应用在前台时ims心跳间隔时长,0表示默认使用ims的值 * * @return */ @Override public int getForegroundHeartbeatInterval() { return 0; } /** * 设置应用在后台时ims心跳间隔时长,0表示默认使用ims的值 * * @return */ @Override public int getBackgroundHeartbeatInterval() { return 0; } /** * 构建握手消息 * * @return */ @Override public MessageProtobuf.Msg getHandshakeMsg() { MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); headBuilder.setMsgId(UUID.randomUUID().toString()); headBuilder.setMsgType(MessageType.HANDSHAKE.getMsgType()); headBuilder.setFromId(userId); headBuilder.setTimestamp(System.currentTimeMillis()); JSONObject jsonObj = new JSONObject(); jsonObj.put("token", token); headBuilder.setExtend(jsonObj.toString()); builder.setHead(headBuilder.build()); return builder.build(); } /** * 构建心跳消息 * * @return */ @Override public MessageProtobuf.Msg getHeartbeatMsg() { MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); headBuilder.setMsgId(UUID.randomUUID().toString()); headBuilder.setMsgType(MessageType.HEARTBEAT.getMsgType()); headBuilder.setFromId(userId); headBuilder.setTimestamp(System.currentTimeMillis()); builder.setHead(headBuilder.build()); return builder.build(); } /** * 服务端返回的消息发送状态报告消息类型 * * @return */ @Override public int getServerSentReportMsgType() { return MessageType.SERVER_MSG_SENT_STATUS_REPORT.getMsgType(); } /** * 客户端提交的消息接收状态报告消息类型 * * @return */ @Override public int getClientReceivedReportMsgType() { return MessageType.CLIENT_MSG_RECEIVED_STATUS_REPORT.getMsgType(); } /** * 设置ims消息发送超时重发次数,0表示默认使用ims的值 * * @return */ @Override public int getResendCount() { return 0; } /** * 设置ims消息发送超时重发间隔时长,0表示默认使用ims的值 * * @return */ @Override public int getResendInterval() { return 0; } }
package com.freddy.chat.im; import com.freddy.chat.bean.AppMessage; import com.freddy.chat.bean.BaseMessage; import com.freddy.chat.bean.ContentMessage; import com.freddy.chat.bean.Head; import com.freddy.chat.utils.StringUtil; import com.freddy.im.protobuf.MessageProtobuf; /** * <p>@ProjectName: BoChat</p> * <p>@ClassName: MessageBuilder.java</p> * <p>@PackageName: com.bochat.app.message</p> * <b> * <p>@Description: 消息转换</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/02/07 17:26</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public class MessageBuilder { /** * 根据聊天消息,生成一条可以能够传输通讯的消息 * * @param msgId * @param type * @param subType * @param fromId * @param toId * @param extend * @param content * @return */ public static AppMessage buildAppMessage(String msgId, int type, int subType, String fromId, String toId, String extend, String content) { AppMessage message = new AppMessage(); Head head = new Head(); head.setMsgId(msgId); head.setMsgType(type); head.setMsgContentType(subType); head.setFromId(fromId); head.setToId(toId); head.setExtend(extend); message.setHead(head); message.setBody(content); return message; } /** * 根据聊天消息,生成一条可以能够传输通讯的消息 * * @param msg * @return */ public static AppMessage buildAppMessage(ContentMessage msg) { AppMessage message = new AppMessage(); Head head = new Head(); head.setMsgId(msg.getMsgId()); head.setMsgType(msg.getMsgType()); head.setMsgContentType(msg.getMsgContentType()); head.setFromId(msg.getFromId()); head.setToId(msg.getToId()); head.setTimestamp(msg.getTimestamp()); head.setExtend(msg.getExtend()); message.setHead(head); message.setBody(msg.getContent()); return message; } /** * 根据聊天消息,生成一条可以能够传输通讯的消息 * * @param msg * @return */ public static AppMessage buildAppMessage(BaseMessage msg) { AppMessage message = new AppMessage(); Head head = new Head(); head.setMsgId(msg.getMsgId()); head.setMsgType(msg.getMsgType()); head.setMsgContentType(msg.getMsgContentType()); head.setFromId(msg.getFromId()); head.setToId(msg.getToId()); head.setExtend(msg.getExtend()); head.setTimestamp(msg.getTimestamp()); message.setHead(head); message.setBody(msg.getContent()); return message; } /** * 根据业务消息对象获取protoBuf消息对应的builder * * @param message * @return */ public static MessageProtobuf.Msg.Builder getProtoBufMessageBuilderByAppMessage(AppMessage message) { MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); headBuilder.setMsgType(message.getHead().getMsgType()); headBuilder.setStatusReport(message.getHead().getStatusReport()); headBuilder.setMsgContentType(message.getHead().getMsgContentType()); if (!StringUtil.isEmpty(message.getHead().getMsgId())) headBuilder.setMsgId(message.getHead().getMsgId()); if (!StringUtil.isEmpty(message.getHead().getFromId())) headBuilder.setFromId(message.getHead().getFromId()); if (!StringUtil.isEmpty(message.getHead().getToId())) headBuilder.setToId(message.getHead().getToId()); if (message.getHead().getTimestamp() != 0) headBuilder.setTimestamp(message.getHead().getTimestamp()); if (!StringUtil.isEmpty(message.getHead().getExtend())) headBuilder.setExtend(message.getHead().getExtend()); if (!StringUtil.isEmpty(message.getBody())) builder.setBody(message.getBody()); builder.setHead(headBuilder); return builder; } /** * 通过protobuf消息对象获取业务消息对象 * * @param protobufMessage * @return */ public static AppMessage getMessageByProtobuf( MessageProtobuf.Msg protobufMessage) { AppMessage message = new AppMessage(); Head head = new Head(); MessageProtobuf.Head protoHead = protobufMessage.getHead(); head.setMsgType(protoHead.getMsgType()); head.setStatusReport(protoHead.getStatusReport()); head.setMsgContentType(protoHead.getMsgContentType()); head.setMsgId(protoHead.getMsgId()); head.setFromId(protoHead.getFromId()); head.setToId(protoHead.getToId()); head.setTimestamp(protoHead.getTimestamp()); head.setExtend(protoHead.getExtend()); message.setHead(head); message.setBody(protobufMessage.getBody()); return message; } }
package com.freddy.chat.im.handler; import com.freddy.chat.bean.AppMessage; /** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: AbstractMessageHandler.java</p> * <p>@PackageName: com.freddy.chat.im.handler</p> * <b> * <p>@Description: 抽象的MessageHandler</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/10 03:41</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public abstract class AbstractMessageHandler implements IMessageHandler { @Override public void execute(AppMessage message) { action(message); } protected abstract void action(AppMessage message); }
package com.freddy.chat.im.handler; import android.util.Log; import com.freddy.chat.bean.AppMessage; import com.freddy.chat.bean.SingleMessage; import com.freddy.chat.event.CEventCenter; import com.freddy.chat.event.Events; /** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: SingleChatMessageHandler.java</p> * <p>@PackageName: com.freddy.chat.im.handler</p> * <b> * <p>@Description: 类描述</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/10 03:43</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public class SingleChatMessageHandler extends AbstractMessageHandler { private static final String TAG = SingleChatMessageHandler.class.getSimpleName(); @Override protected void action(AppMessage message) { Log.d(TAG, "收到单聊消息,message=" + message); SingleMessage msg = new SingleMessage(); msg.setMsgId(message.getHead().getMsgId()); msg.setMsgType(message.getHead().getMsgType()); msg.setMsgContentType(message.getHead().getMsgContentType()); msg.setFromId(message.getHead().getFromId()); msg.setToId(message.getHead().getToId()); msg.setTimestamp(message.getHead().getTimestamp()); msg.setExtend(message.getHead().getExtend()); msg.setContent(message.getBody()); CEventCenter.dispatchEvent(Events.CHAT_SINGLE_MESSAGE, 0, 0, msg); } }
package com.freddy.chat.im.handler; import android.util.Log; import com.freddy.chat.bean.AppMessage; /** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: GroupChatMessageHandler.java</p> * <p>@PackageName: com.freddy.chat.im.handler</p> * <b> * <p>@Description: 类描述</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/10 03:43</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public class GroupChatMessageHandler extends AbstractMessageHandler { private static final String TAG = GroupChatMessageHandler.class.getSimpleName(); @Override protected void action(AppMessage message) { Log.d(TAG, "收到群聊消息,message=" + message); } }
package com.freddy.chat.im.handler; import android.util.SparseArray; import com.freddy.chat.im.MessageType; /** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: MessageHandlerFactory.java</p> * <p>@PackageName: com.freddy.chat.im.handler</p> * <b> * <p>@Description: 消息处理handler工厂</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/10 03:44</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public class MessageHandlerFactory { private MessageHandlerFactory() { } private static final SparseArray<IMessageHandler> HANDLERS = new SparseArray<>(); static { /** 单聊消息处理handler */ HANDLERS.put(MessageType.SINGLE_CHAT.getMsgType(), new SingleChatMessageHandler()); /** 群聊消息处理handler */ HANDLERS.put(MessageType.GROUP_CHAT.getMsgType(), new GroupChatMessageHandler()); /** 服务端返回的消息发送状态报告处理handler */ HANDLERS.put(MessageType.SERVER_MSG_SENT_STATUS_REPORT.getMsgType(), new ServerReportMessageHandler()); } /** * 根据消息类型获取对应的处理handler * * @param msgType * @return */ public static IMessageHandler getHandlerByMsgType(int msgType) { return HANDLERS.get(msgType); } }
package com.freddy.chat.im; /** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: MessageType.java</p> * <p>@PackageName: com.freddy.chat.im</p> * <b> * <p>@Description: 消息类型</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/08 00:04</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public enum MessageType { /* * 握手消息 */ HANDSHAKE(1001), /* * 心跳消息 */ HEARTBEAT(1002), /* * 客户端提交的消息接收状态报告 */ CLIENT_MSG_RECEIVED_STATUS_REPORT(1009), /* * 服务端返回的消息发送状态报告 */ SERVER_MSG_SENT_STATUS_REPORT(1010), /** * 单聊消息 */ SINGLE_CHAT(2001), /** * 群聊消息 */ GROUP_CHAT(3001); private int msgType; MessageType(int msgType) { this.msgType = msgType; } public int getMsgType() { return this.msgType; } public enum MessageContentType { /** * 文本消息 */ TEXT(101), /** * 图片消息 */ IMAGE(102), /** * 语音消息 */ VOICE(103); private int msgContentType; MessageContentType(int msgContentType) { this.msgContentType = msgContentType; } public int getMsgContentType() { return this.msgContentType; } } }
package com.freddy.chat.im; import com.freddy.im.listener.IMSConnectStatusCallback; /** * <p>@ProjectName: NettyChat</p> * <p>@ClassName: IMSConnectStatusListener.java</p> * <p>@PackageName: com.freddy.chat.im</p> * <b> * <p>@Description: 类描述</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/08 00:31</p> * <p>@email: [url=mailto:chenshichao@outlook.com]chenshichao@outlook.com[/url]</p> */ public class IMSConnectStatusListener implements IMSConnectStatusCallback { @Override public void onConnecting() { } @Override public void onConnected() { } @Override public void onConnectFailed() { } }
17-1.jpg (37.97 KB, 下载次数: 2029)
17-2.jpg (63.87 KB, 下载次数: 1821)
16a42c85e653b88c11aaa.gif (205.6 KB, 下载次数: 1867)
19-1.png (8.17 KB, 下载次数: 1830)
19-2.png (7.5 KB, 下载次数: 1905)
来源:即时通讯网 - 即时通讯开发者社区!
轻量级开源移动端即时通讯框架。
快速入门 / 性能 / 指南 / 提问
轻量级Web端即时通讯框架。
详细介绍 / 精编源码 / 手册教程
移动端实时音视频框架。
详细介绍 / 性能测试 / 安装体验
基于MobileIMSDK的移动IM系统。
详细介绍 / 产品截图 / 安装体验
一套产品级Web端IM系统。
详细介绍 / 产品截图 / 演示视频
引用此评论
引用:李瑞啊 发表于 2019-07-22 08:37 收藏啦,以后想做安卓的时候再来学习,虽然我现在做的是c++,但我也有一颗做java的心
引用:JackJiang 发表于 2019-07-22 09:40 后面怕是得玩Kotlin了
引用:李瑞啊 发表于 2019-07-23 08:42
引用:踏雪寻梅 发表于 2019-07-23 08:57 人生苦短,我学python
引用:JackJiang 发表于 2019-07-23 09:05 现在的工作是做大数据还是AI?
引用:LeeTg 发表于 2020-05-19 09:07 你好,我是初次学习netty,在根据这篇文章(http://www.52im.net/forum.php?mod=viewthread&tid=2671)搭建 ...
引用:JackJiang 发表于 2020-05-19 10:43 这个文章以及它的带码主要是学习用的,没有经过生产使用,经不起推敲。 你要么研究一下MobileIMSDK框 ...
精华主题数超过100个。
连续任职达2年以上的合格正式版主
为论区做出突出贡献的开发者、版主等。
本人属:鸡
本人属:龙
本人属:鼠
本人属:牛
本人属:虎
本人属:兔
本人属:蛇
本人属:马
本人属:羊
本人属:猴
Copyright © 2014-2024 即时通讯网 - 即时通讯开发者社区 / 版本 V4.4
苏州网际时代信息科技有限公司 (苏ICP备16005070号-1)
Processed in 0.156250 second(s), 45 queries , Gzip On.