版权说明:本文是基于开源LumaQQ下,开发者不直接参与QQ协议的分析工作,移植到Android平台纯粹是为了方便学习和研究之用,并且没有产生任何直接的经济效益,并且纯粹是个人的技术学习研究行为,与本人所在单位没有任何关系。此文的读者在使用过程产生的效益和涉及的法律责任与本人没有直接关系。如果影响到您或您的公司利益,敬请谅解并且与我联系,本人会第一时间作出处理。
作为一个IM工具,如果没有发送、接收消息的功能岂不是搞笑?今天我们就开始进入LumaQQ的核心部分,消息的发送和接收。其中发送部分很简单,前面的文章有过介绍,今天主要就消息的接收部分来做详细的说明。
QQ的消息大致分为普通消息、群消息、系统消息和临时消息。普通消息就是QQ用户发给你的消息;群消息就是群中的聊天消息;系统消息就是腾讯发给你的一些系统通知;临时消息就是用户创建的临时会话信息。这么多种消息我们如何来管理呢?在这里我们来模拟操作系统的消息处理机制,实现一个消息队列MessageQueue,这个消息队列类大致如下:
- public class MessageQueue {
- // 总队列
- private Queue<InPacket> queue;
- // 系统消息队列
- private Queue<InPacket> sysQueue;
- // 延迟处理队列
- private Queue<InPacket> postponeQueue;
- // 用户消息队列映射哈希表
- private Map<Integer, Queue<InPacket>> userQueueMap;
- // 用户临时会话消息映射哈希表
- private Map<Integer, Queue<InPacket>> tempSessionQueueMap;
- // 短消息队列
- private Queue<InPacket> smsQueue;
- // true表示收到的消息应该延迟处理
- private boolean postpone;
- public MessageQueue() {
- }
- /**
- * 清空所有数据
- */
- public void clear() {
- }
- /**
- * 添加一个短消息到队列末尾
- *
- * @param in
- * 要添加的消息包对象
- */
- public void putSMS(InPacket in) {
- }
- /**
- * 得到队列中第一条短消息,并且把它从队列中删除
- *
- * @return
- * InPacket,如果队列为空,返回null
- */
- public InPacket getSMS() {
- }
- /**
- * 得到下一条手机短信
- *
- * @param qq
- * @return
- */
- public InPacket getSMS(int qq) {
- }
- /**
- * 得到下一条手机短信
- *
- * @param mobile
- * @return
- */
- public InPacket getSMS(String mobile) {
- }
- /**
- * 把一个普通消息包推入消息队列
- * @param packet
- * 消息包对象
- */
- public void putMessage(BasicInPacket packet) {
- }
- /**
- * @param packet
- * 消息包对象
- * @param global
- * true表示添加这个消息到总队列中。推到总队列中的效果是这条消息会在tray中
- * 闪烁。有时候消息是不需要在tray中闪烁的,比如把群消息设为只显示计数,不显示
- * 提示时。
- */
- public void putMessage(InPacket packet, boolean global) {
- }
- /**
- * 把临时会话消息推入队列
- *
- * @param packet
- */
- protected void putTempSessionMessage(ReceiveIMPacket packet) {
- }
- /**
- * 把一个系统消息推入队列
- * @param packet
- */
- public void putSystemMessage(BasicInPacket packet) {
- }
- /**
- * 得到一条普通消息,并把他从队列中删除
- *
- * @param qq
- * 发送消息的好友QQ号
- * @return
- * 如果有消息在返回消息,否则返回null
- */
- public InPacket getMessage(int qq) {
- }
- /**
- * 得到一条普通消息,不把他从队列中删除
- *
- * @param qq
- * 发送消息的好友QQ号
- * @return
- * 如果有消息在返回消息,否则返回null
- */
- public InPacket peekMessage(int qq) {
- }
- /**
- * 把qq号指定的好友或者群的所有消息删除
- *
- * @param qq
- * 可能是好友的QQ号,也可能是群的内部ID
- */
- public void removeMessage(int qq) {
- }
- /**
- * 得到一条普通消息,这条消息是该组内队列的第一条
- *
- * @param g
- * Group
- * @return
- * 如果有则返回消息,否则返回null
- */
- public InPacket getGroupMessage(Group g) {
- }
- /**
- * 得到系统消息队列的第一个包,但是不删除它
- *
- * @return
- * 系统消息包对象
- */
- public InPacket peekSystemMessage() {
- }
- /**
- * 得到一条系统消息,并把他从队列删除
- *
- * @return
- * 如果有消息,返回消息,否则返回null
- */
- public InPacket getSystemMessage() {
- }
- /**
- * 检查是否某个好友还有消息未读
- *
- * @param qqNum
- * 好友QQ号
- * @return
- * true如果有消息未读
- */
- public boolean hasMessage(int qqNum) {
- }
- /**
- * 检查是否有某个用户的临时会话消息
- *
- * @param qqNum
- * QQ号
- * @return
- * true表示有临时会话消息未读
- */
- public boolean hasTempSessionMessage(int qqNum) {
- }
- /**
- * 得到下一条临时会话消息
- *
- * @param qqNum
- * @return
- */
- public InPacket getTempSessionMessage(int qqNum) {
- }
- /**
- * 得到下一条临时会话消息,不从队列中删除
- *
- * @param qqNum
- * @return
- */
- public InPacket peekTempSessionMessage(int qqNum) {
- }
- /**
- * 检查是否某个群下面有讨论组的消息,如果父群id是0,则检查
- * 是否有多人对话消息
- *
- * @param parentClusterId
- * 父群id,0表示多人对话容器
- * @return
- * 子群id,如果为-1表示没有子群有消息
- */
- public int hasSubClusterIM(int parentClusterId) {
- }
- /**
- * 检查某个组是否有消息未读
- *
- * @param g
- * Group
- * @return
- * true如果有消息未读
- */
- public boolean hasGroupMessage(Group g) {
- }
- /**
- * @return
- * true如果还有任何消息未读
- */
- public boolean hasNext() {
- }
- /**
- * @return
- * true如果还有系统消息未读
- */
- public boolean hasSystemMessage() {
- }
- /**
- * @return
- * true如果还有短消息
- */
- public boolean hasSMS() {
- }
- /**
- * 好友的下一条消息是不是临时会话消息
- *
- * @param qq
- * @return
- */
- public boolean isNextTempSessionMessage(int qq) {
- }
- /**
- * @return
- * 下一条消息的发送者的QQ号,如果是0,表示是系统消息,-1表示
- * 无消息如果是群消息,返回的将是群的内部ID
- */
- public int nextSender() {
- }
- /**
- * 返回下一个消息的来源,对于普通消息,返回QQ_IM_FROM_FRIEND,对于
- * 系统消息,返回QQ_IM_FROM_SYS,对于群消息,有两种情况,因为群消息
- * 包含了普通消息和通知消息,对于普通消息,我们返回QQ_IM_FROM_CLUSTER,
- * 对于通知消息,我们返回QQ_IM_FROM_SYS
- *
- * @return
- * 消息来源标识常量
- */
- public int nextMessageSource() {
- }
- /**
- * 返回该组内下一条消息发送者的QQ号
- *
- * @param g
- * Group
- * @return
- * QQ号,如果没有消息,返回-1
- */
- public int nextGroupSender(Group g) {
- }
- /**
- * @return
- * 下一个应该闪烁的消息的发送者
- */
- public int nextBlinkableIMSender() {
- }
- /**
- * @return
- * 下一条群消息的群号,-1表示没有
- */
- public int nextClusterIMSender() {
- }
- /**
- * 一个消息在好友列表还没得到之前到达了,延迟处理这个消息
- *
- * @param packet
- * 消息包
- */
- public void postponeMessage(InPacket packet) {
- }
- /**
- * 返回下一个延迟处理的消息
- *
- * @return
- * 如果有则返回消息,没有返回null
- */
- public InPacket getPostponedMessage() {
- }
- /**
- * @return Returns the postpone.
- */
- public synchronized boolean isPostpone() {
- }
- /**
- * @param postpone The postpone to set.
- */
- public synchronized void setPostpone(boolean postpone) {
- }
- }
有了这个消息队列,我们就可以随时来处理QQ的消息,不会因为没来及读取消息导致消息的丢失。
QQ消息如此之多只是有个管理的队列显然不够,我们还需要对这些消息进行处理,我们来实现一个处理消息的辅助类MessageHelper,大致代码如下,里面我们现在只处理了普通消息- public class MessageHelper {
- private MainShell main;
- // 分片缓冲,有的长消息会变成几个分片发送,需要保存起来等待所有分片完成
- // key是消息id,value是个Object数组,保存了消息的分片
- private Map<Integer, Object[]> fragmentCache;
- public MessageHelper(MainShell main) {
- this.main = main;
- fragmentCache = new HashMap<Integer, Object[]>();
- }
- /**
- * 把字节数组转换为String,它为我们处理缺省表情的问题
- *
- * @param b
- * 消息字节数组
- * @return String
- */
- public String convertBytes(byte[] b) {
- StringBuffer sb = new StringBuffer();
- int offset = 0;
- int length = 0;
- for (int i = 0; i < b.length; i++) {
- if (b[i] == QQ.QQ_TAG_DEFAULT_FACE) {
- sb.append(Util.getString(b, offset, length));
- sb.append((char) b[i]).append((char) (b[i + 1] & 0xFF));
- i++;
- offset = i + 1;
- length = 0;
- } else
- length++;
- }
- if (length > 0)
- sb.append(Util.getString(b, offset, length));
- return sb.toString();
- }
- /**
- * 检查这个消息是完整消息还是分片
- *
- * @return true表示这个消息是分片消息
- */
- private boolean isFragment(NormalIM im) {
- return im.totalFragments > 1;
- }
- /**
- * 检查这个消息是完整消息还是分片
- *
- * @return true表示这个消息是分片消息
- */
- private boolean isFragment(ClusterIM im) {
- return im.fragmentCount > 1;
- }
- /**
- * 添加一个普通消息分片
- *
- * @param im
- */
- private void addFragment(NormalIM im) {
- Object[] fragments = fragmentCache.get(im.messageId);
- if (fragments == null || fragments.length != im.totalFragments) {
- fragments = new Object[im.totalFragments];
- fragmentCache.put(im.messageId, fragments);
- }
- fragments[im.fragmentSequence] = im;
- }
- /**
- * 添加一个群消息分片
- *
- * @param im
- */
- private void addFragment(ClusterIM im) {
- Object[] fragments = fragmentCache.get(im.messageId);
- if (fragments == null || fragments.length != im.fragmentCount) {
- fragments = new Object[im.fragmentCount];
- fragmentCache.put(im.messageId, fragments);
- }
- fragments[im.fragmentSequence] = im;
- }
- /**
- * 得到完整的消息,同时把这个消息从分片缓冲中清楚。调用此方法前,必须先用 isMessageComplete()判断分片是否都已经收到
- *
- * @param messageId
- * 消息id
- * @return ClusterIM对象
- */
- private ClusterIM getIntegratedClusterIM(int messageId) {
- Object[] fragments = fragmentCache.remove(messageId);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- for (Object f : fragments) {
- try {
- baos.write(((ClusterIM) f).messageBytes);
- } catch (IOException e) {
- }
- }
- ClusterIM ret = (ClusterIM) fragments[fragments.length - 1];
- ret.messageBytes = baos.toByteArray();
- ret.message = convertBytes(ret.messageBytes);
- return ret;
- }
- /**
- * 得到完整的普通消息
- *
- * @param messageId
- * 消息ID
- * @return NormalIM对象
- */
- private NormalIM getIntegratedNormalIM(int messageId) {
- Object[] fragments = fragmentCache.remove(messageId);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- for (Object f : fragments) {
- try {
- baos.write(((NormalIM) f).messageBytes);
- } catch (IOException e) {
- }
- }
- NormalIM ret = (NormalIM) fragments[0];
- ret.message = NormalIMFormalizer.deformalize(baos.toByteArray());
- return ret;
- }
- /**
- * 检查是否一个长消息的分片都已经收到了
- *
- * @param messageId
- * 消息id
- * @return true表示已经收到
- */
- private boolean isMessageComplete(int messageId) {
- if (!fragmentCache.containsKey(messageId))
- return false;
- Object[] fragments = fragmentCache.get(messageId);
- for (Object f : fragments) {
- if (f == null)
- return false;
- }
- return true;
- }
- /**
- * 推入一条消息,这个方法会检查是否好友列表已经 得到,如果没有,推入延迟队列
- *
- * @param packet
- * 消息包
- */
- public void putNormalIM(ReceiveIMPacket packet) {
- // 如果现在是延迟处理
- MessageQueue mq = main.getMessageQueue();
- if (mq.isPostpone()) {
- mq.postponeMessage(packet);
- return;
- }
- // 如果这个消息是分片消息,如果这个消息已经完成,则继续处理,否则推入分片缓冲
- if (isFragment(packet.normalIM)) {
- addFragment(packet.normalIM);
- if (isMessageComplete(packet.normalIM.messageId)) {
- packet.normalIM = getIntegratedNormalIM(packet.normalIM.messageId);
- } else {
- return;
- }
- } else {
- packet.normalIM.message = NormalIMFormalizer
- .deformalize(packet.normalIM.messageBytes);
- }
- // 得到好友在model中的位置,但是有可能为null,因为也许这是用户的第一次登陆
- // 其好友列表还没得到,但是这时候有消息来了,对于
- // 这种情况,需要特殊处理一下,基本的方法是把消息推入延迟处理队列
- User f = ModelRegistry.getUser(packet.normalHeader.sender);
- boolean iAmHisStranger = packet.header.type == QQ.QQ_RECV_IM_STRANGER;
- boolean noSuchUser = f == null || f.group.isCluster();
- // boolean heIsMyStranger = noSuchUser || f.group.isStranger();
- boolean heIsMyBlacklist = f != null && f.group.isBlackList();
- if (heIsMyBlacklist || noSuchUser && iAmHisStranger) {
- return;
- }
- // if (heIsMyStranger) return;
- UIHelper uihelper = main.getUIHelper();
- // 如果不存在这个用户,添加到陌生人
- if (noSuchUser) {
- f = new User();
- f.qq = packet.normalHeader.sender;
- f.nick = String.valueOf(f.qq);
- f.displayName = f.nick;
- uihelper.addUser(f, GroupType.STRANGER_GROUP);
- main.getUIHelper().refreshGroup();
- main.getClient().user_GetInfo(f.qq);
- }
- uihelper.appendMessage(f, packet.normalIM, packet.normalHeader);
- // 推入队列
- mq.putMessage(packet);
- }
- }
有了这两个基础的类之后我们就可以对QQ消息事件进行处理了。QQ消息方面的事件很多,这里我们只处理一下普通消息的事件
- protected void OnQQEvent(QQEvent e) {
- switch (e.type) {
- case QQEvent.IM_RECEIVED:
- processReceiveNormalIM(e);
- break;
- }
- }
- /**
- * 处理收到消息事件
- *
- * @param e
- * QQEvent
- */
- private void processReceiveNormalIM(QQEvent e) {
- // 得到包,推入消息队列
- ReceiveIMPacket packet = (ReceiveIMPacket)e.getSource();
- main.getMessageHelper().putNormalIM(packet);
- }
代码在这里,请点击下载http://download.csdn.net/source/698372