lumaQQ移植到Android之消息篇

版权说明:本文是基于开源LumaQQ下,开发者不直接参与QQ协议的分析工作,移植到Android平台纯粹是为了方便学习和研究之用,并且没有产生任何直接的经济效益,并且纯粹是个人的技术学习研究行为,与本人所在单位没有任何关系。此文的读者在使用过程产生的效益和涉及的法律责任与本人没有直接关系。如果影响到您或您的公司利益,敬请谅解并且与我联系,本人会第一时间作出处理。

作为一个IM工具,如果没有发送、接收消息的功能岂不是搞笑?今天我们就开始进入LumaQQ的核心部分,消息的发送和接收。其中发送部分很简单,前面的文章有过介绍,今天主要就消息的接收部分来做详细的说明。

       QQ的消息大致分为普通消息、群消息、系统消息和临时消息。普通消息就是QQ用户发给你的消息;群消息就是群中的聊天消息;系统消息就是腾讯发给你的一些系统通知;临时消息就是用户创建的临时会话信息。这么多种消息我们如何来管理呢?在这里我们来模拟操作系统的消息处理机制,实现一个消息队列MessageQueue,这个消息队列类大致如下:

  1. public class MessageQueue {
  2.     // 总队列
  3.     private Queue<InPacket> queue;
  4.     // 系统消息队列
  5.     private Queue<InPacket> sysQueue;
  6.     // 延迟处理队列
  7.     private Queue<InPacket> postponeQueue;
  8.     // 用户消息队列映射哈希表
  9.     private Map<Integer, Queue<InPacket>> userQueueMap; 
  10.     // 用户临时会话消息映射哈希表
  11.     private Map<Integer, Queue<InPacket>> tempSessionQueueMap;
  12.     // 短消息队列
  13.     private Queue<InPacket> smsQueue;
  14.     
  15.     // true表示收到的消息应该延迟处理
  16.     private boolean postpone;
  17.     
  18.     public MessageQueue() {
  19.     }
  20.     
  21.     /**
  22.      * 清空所有数据
  23.      */
  24.     public void clear() {
  25.     }
  26.     
  27.     /**
  28.      * 添加一个短消息到队列末尾
  29.      * 
  30.      * @param in
  31.      *      要添加的消息包对象
  32.      */
  33.     public void putSMS(InPacket in) {
  34.     }
  35.     
  36.     /**
  37.      * 得到队列中第一条短消息,并且把它从队列中删除
  38.      * 
  39.      * @return 
  40.      *      InPacket,如果队列为空,返回null
  41.      */
  42.     public InPacket getSMS() {      
  43.     }
  44.     
  45.     /**
  46.      * 得到下一条手机短信
  47.      * 
  48.      * @param qq
  49.      * @return
  50.      */
  51.     public InPacket getSMS(int qq) {        
  52.     }
  53.     
  54.     /**
  55.      * 得到下一条手机短信
  56.      * 
  57.      * @param mobile
  58.      * @return
  59.      */
  60.     public InPacket getSMS(String mobile) {     
  61.     }
  62.     /**
  63.      * 把一个普通消息包推入消息队列
  64.      * @param packet 
  65.      *      消息包对象
  66.      */
  67.     public void putMessage(BasicInPacket packet) {
  68.     }
  69.     
  70.     /**
  71.      * @param packet 
  72.      *      消息包对象
  73.      * @param global
  74.      *      true表示添加这个消息到总队列中。推到总队列中的效果是这条消息会在tray中
  75.      *      闪烁。有时候消息是不需要在tray中闪烁的,比如把群消息设为只显示计数,不显示
  76.      *      提示时。
  77.      */
  78.     public void putMessage(InPacket packet, boolean global) {       
  79.     }
  80.     
  81.     /**
  82.      * 把临时会话消息推入队列
  83.      * 
  84.      * @param packet
  85.      */
  86.     protected void putTempSessionMessage(ReceiveIMPacket packet) {      
  87.     }
  88.     /**
  89.      * 把一个系统消息推入队列
  90.      * @param packet
  91.      */
  92.     public void putSystemMessage(BasicInPacket packet) {        
  93.     }
  94.     /**
  95.      * 得到一条普通消息,并把他从队列中删除
  96.      * 
  97.      * @param qq 
  98.      *      发送消息的好友QQ号
  99.      * @return 
  100.      *      如果有消息在返回消息,否则返回null
  101.      */
  102.     public InPacket getMessage(int qq) {
  103.     }
  104.     
  105.     /**
  106.      * 得到一条普通消息,不把他从队列中删除
  107.      * 
  108.      * @param qq 
  109.      *      发送消息的好友QQ号
  110.      * @return 
  111.      *      如果有消息在返回消息,否则返回null
  112.      */
  113.     public InPacket peekMessage(int qq) {       
  114.     }
  115.     
  116.     /**
  117.      * 把qq号指定的好友或者群的所有消息删除
  118.      * 
  119.      * @param qq 
  120.      *      可能是好友的QQ号,也可能是群的内部ID
  121.      */
  122.     public void removeMessage(int qq) {     
  123.     }
  124.     
  125.     /**
  126.      * 得到一条普通消息,这条消息是该组内队列的第一条
  127.      * 
  128.      * @param g 
  129.      *      Group
  130.      * @return 
  131.      *      如果有则返回消息,否则返回null
  132.      */
  133.     public InPacket getGroupMessage(Group g) {      
  134.     }
  135.     /**
  136.      * 得到系统消息队列的第一个包,但是不删除它
  137.      * 
  138.      * @return
  139.      *      系统消息包对象
  140.      */
  141.     public InPacket peekSystemMessage() {
  142.     }
  143.     
  144.     /**
  145.      * 得到一条系统消息,并把他从队列删除
  146.      * 
  147.      * @return 
  148.      *      如果有消息,返回消息,否则返回null
  149.      */
  150.     public InPacket getSystemMessage() {        
  151.     }
  152.     /**
  153.      * 检查是否某个好友还有消息未读
  154.      * 
  155.      * @param qqNum
  156.      *      好友QQ号
  157.      * @return 
  158.      *      true如果有消息未读
  159.      */
  160.     public boolean hasMessage(int qqNum) {
  161.     }
  162.     
  163.     /**
  164.      * 检查是否有某个用户的临时会话消息
  165.      * 
  166.      * @param qqNum
  167.      *      QQ号
  168.      * @return
  169.      *      true表示有临时会话消息未读
  170.      */
  171.     public boolean hasTempSessionMessage(int qqNum) {
  172.     }
  173.     
  174.     /**
  175.      * 得到下一条临时会话消息
  176.      * 
  177.      * @param qqNum
  178.      * @return
  179.      */
  180.     public InPacket getTempSessionMessage(int qqNum) {
  181.     }
  182.     
  183.     /**
  184.      * 得到下一条临时会话消息,不从队列中删除
  185.      * 
  186.      * @param qqNum
  187.      * @return
  188.      */
  189.     public InPacket peekTempSessionMessage(int qqNum) {
  190.     }
  191.     
  192.     /**
  193.      * 检查是否某个群下面有讨论组的消息,如果父群id是0,则检查
  194.      * 是否有多人对话消息
  195.      * 
  196.      * @param parentClusterId
  197.      *      父群id,0表示多人对话容器
  198.      * @return
  199.      *      子群id,如果为-1表示没有子群有消息
  200.      */
  201.     public int hasSubClusterIM(int parentClusterId) {
  202.     }
  203.     
  204.     /**
  205.      * 检查某个组是否有消息未读
  206.      * 
  207.      * @param g 
  208.      *      Group
  209.      * @return 
  210.      *      true如果有消息未读
  211.      */
  212.     public boolean hasGroupMessage(Group g) {
  213.     }
  214.     /**
  215.      * @return 
  216.      *      true如果还有任何消息未读
  217.      */
  218.     public boolean hasNext() {
  219.     }
  220.     /**
  221.      * @return 
  222.      *      true如果还有系统消息未读
  223.      */
  224.     public boolean hasSystemMessage() {
  225.     }
  226.     
  227.     /**
  228.      * @return 
  229.      *      true如果还有短消息
  230.      */
  231.     public boolean hasSMS() {
  232.     }
  233.     
  234.     /**
  235.      * 好友的下一条消息是不是临时会话消息
  236.      * 
  237.      * @param qq
  238.      * @return
  239.      */
  240.     public boolean isNextTempSessionMessage(int qq) {   
  241.     }
  242.     /**
  243.      * @return 
  244.      *      下一条消息的发送者的QQ号,如果是0,表示是系统消息,-1表示
  245.      *      无消息如果是群消息,返回的将是群的内部ID
  246.      */
  247.     public int nextSender() {       
  248.     }
  249.     
  250.     /**
  251.      * 返回下一个消息的来源,对于普通消息,返回QQ_IM_FROM_FRIEND,对于
  252.      * 系统消息,返回QQ_IM_FROM_SYS,对于群消息,有两种情况,因为群消息
  253.      * 包含了普通消息和通知消息,对于普通消息,我们返回QQ_IM_FROM_CLUSTER,
  254.      * 对于通知消息,我们返回QQ_IM_FROM_SYS
  255.      * 
  256.      * @return
  257.      *      消息来源标识常量
  258.      */
  259.     public int nextMessageSource() {        
  260.     }
  261.     
  262.     /**
  263.      * 返回该组内下一条消息发送者的QQ号
  264.      * 
  265.      * @param g
  266.      *      Group
  267.      * @return
  268.      *      QQ号,如果没有消息,返回-1
  269.      */
  270.     public int nextGroupSender(Group g) {
  271.     }
  272.     
  273.     /**
  274.      * @return
  275.      *      下一个应该闪烁的消息的发送者
  276.      */
  277.     public int nextBlinkableIMSender() {
  278.     }
  279.     
  280.     /**
  281.      * @return
  282.      *      下一条群消息的群号,-1表示没有
  283.      */
  284.     public int nextClusterIMSender() {
  285.     }
  286.     
  287.     /**
  288.      * 一个消息在好友列表还没得到之前到达了,延迟处理这个消息
  289.      * 
  290.      * @param packet 
  291.      *      消息包
  292.      */
  293.     public void postponeMessage(InPacket packet) {
  294.     }
  295.     
  296.     /**
  297.      * 返回下一个延迟处理的消息
  298.      * 
  299.      * @return 
  300.      *      如果有则返回消息,没有返回null
  301.      */
  302.     public InPacket getPostponedMessage() {
  303.     }
  304.     /**
  305.      * @return Returns the postpone.
  306.      */
  307.     public synchronized boolean isPostpone() {
  308.     }
  309.     /**
  310.      * @param postpone The postpone to set.
  311.      */
  312.     public synchronized void setPostpone(boolean postpone) {
  313.     }
  314. }

有了这个消息队列,我们就可以随时来处理QQ的消息,不会因为没来及读取消息导致消息的丢失。

  QQ
消息如此之多只是有个管理的队列显然不够,我们还需要对这些消息进行处理,我们来实现一个处理消息的辅助类MessageHelper,大致代码如下,里面我们现在只处理了普通消息
  1. public class MessageHelper {
  2.     private MainShell main;
  3.     // 分片缓冲,有的长消息会变成几个分片发送,需要保存起来等待所有分片完成
  4.     // key是消息id,value是个Object数组,保存了消息的分片
  5.     private Map<Integer, Object[]> fragmentCache;
  6.     public MessageHelper(MainShell main) {
  7.         this.main = main;
  8.         fragmentCache = new HashMap<Integer, Object[]>();
  9.     }
  10.     /**
  11.      * 把字节数组转换为String,它为我们处理缺省表情的问题
  12.      * 
  13.      * @param b
  14.      *            消息字节数组
  15.      * @return String
  16.      */
  17.     public String convertBytes(byte[] b) {
  18.         StringBuffer sb = new StringBuffer();
  19.         int offset = 0;
  20.         int length = 0;
  21.         for (int i = 0; i < b.length; i++) {
  22.             if (b[i] == QQ.QQ_TAG_DEFAULT_FACE) {
  23.                 sb.append(Util.getString(b, offset, length));
  24.                 sb.append((char) b[i]).append((char) (b[i + 1] & 0xFF));
  25.                 i++;
  26.                 offset = i + 1;
  27.                 length = 0;
  28.             } else
  29.                 length++;
  30.         }
  31.         if (length > 0)
  32.             sb.append(Util.getString(b, offset, length));
  33.         return sb.toString();
  34.     }
  35.     /**
  36.      * 检查这个消息是完整消息还是分片
  37.      * 
  38.      * @return true表示这个消息是分片消息
  39.      */
  40.     private boolean isFragment(NormalIM im) {
  41.         return im.totalFragments > 1;
  42.     }
  43.     /**
  44.      * 检查这个消息是完整消息还是分片
  45.      * 
  46.      * @return true表示这个消息是分片消息
  47.      */
  48.     private boolean isFragment(ClusterIM im) {
  49.         return im.fragmentCount > 1;
  50.     }
  51.     /**
  52.      * 添加一个普通消息分片
  53.      * 
  54.      * @param im
  55.      */
  56.     private void addFragment(NormalIM im) {
  57.         Object[] fragments = fragmentCache.get(im.messageId);
  58.         if (fragments == null || fragments.length != im.totalFragments) {
  59.             fragments = new Object[im.totalFragments];
  60.             fragmentCache.put(im.messageId, fragments);
  61.         }
  62.         fragments[im.fragmentSequence] = im;
  63.     }
  64.     /**
  65.      * 添加一个群消息分片
  66.      * 
  67.      * @param im
  68.      */
  69.     private void addFragment(ClusterIM im) {
  70.         Object[] fragments = fragmentCache.get(im.messageId);
  71.         if (fragments == null || fragments.length != im.fragmentCount) {
  72.             fragments = new Object[im.fragmentCount];
  73.             fragmentCache.put(im.messageId, fragments);
  74.         }
  75.         fragments[im.fragmentSequence] = im;
  76.     }
  77.     /**
  78.      * 得到完整的消息,同时把这个消息从分片缓冲中清楚。调用此方法前,必须先用 isMessageComplete()判断分片是否都已经收到
  79.      * 
  80.      * @param messageId
  81.      *            消息id
  82.      * @return ClusterIM对象
  83.      */
  84.     private ClusterIM getIntegratedClusterIM(int messageId) {
  85.         Object[] fragments = fragmentCache.remove(messageId);
  86.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  87.         for (Object f : fragments) {
  88.             try {
  89.                 baos.write(((ClusterIM) f).messageBytes);
  90.             } catch (IOException e) {
  91.             }
  92.         }
  93.         ClusterIM ret = (ClusterIM) fragments[fragments.length - 1];
  94.         ret.messageBytes = baos.toByteArray();
  95.         ret.message = convertBytes(ret.messageBytes);
  96.         return ret;
  97.     }
  98.     /**
  99.      * 得到完整的普通消息
  100.      * 
  101.      * @param messageId
  102.      *            消息ID
  103.      * @return NormalIM对象
  104.      */
  105.     private NormalIM getIntegratedNormalIM(int messageId) {
  106.         Object[] fragments = fragmentCache.remove(messageId);
  107.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  108.         for (Object f : fragments) {
  109.             try {
  110.                 baos.write(((NormalIM) f).messageBytes);
  111.             } catch (IOException e) {
  112.             }
  113.         }
  114.         NormalIM ret = (NormalIM) fragments[0];
  115.         ret.message = NormalIMFormalizer.deformalize(baos.toByteArray());
  116.         return ret;
  117.     }
  118.     /**
  119.      * 检查是否一个长消息的分片都已经收到了
  120.      * 
  121.      * @param messageId
  122.      *            消息id
  123.      * @return true表示已经收到
  124.      */
  125.     private boolean isMessageComplete(int messageId) {
  126.         if (!fragmentCache.containsKey(messageId))
  127.             return false;
  128.         Object[] fragments = fragmentCache.get(messageId);
  129.         for (Object f : fragments) {
  130.             if (f == null)
  131.                 return false;
  132.         }
  133.         return true;
  134.     }
  135.     /**
  136.      * 推入一条消息,这个方法会检查是否好友列表已经 得到,如果没有,推入延迟队列
  137.      * 
  138.      * @param packet
  139.      *            消息包
  140.      */
  141.     public void putNormalIM(ReceiveIMPacket packet) {
  142.         // 如果现在是延迟处理
  143.         MessageQueue mq = main.getMessageQueue();
  144.         if (mq.isPostpone()) {
  145.             mq.postponeMessage(packet);
  146.             return;
  147.         }
  148.         // 如果这个消息是分片消息,如果这个消息已经完成,则继续处理,否则推入分片缓冲
  149.         if (isFragment(packet.normalIM)) {
  150.             addFragment(packet.normalIM);
  151.             if (isMessageComplete(packet.normalIM.messageId)) {
  152.                 packet.normalIM = getIntegratedNormalIM(packet.normalIM.messageId);
  153.             } else {
  154.                 return;
  155.             }
  156.         } else {
  157.             packet.normalIM.message = NormalIMFormalizer
  158.                     .deformalize(packet.normalIM.messageBytes);
  159.         }
  160.         // 得到好友在model中的位置,但是有可能为null,因为也许这是用户的第一次登陆
  161.         // 其好友列表还没得到,但是这时候有消息来了,对于
  162.         // 这种情况,需要特殊处理一下,基本的方法是把消息推入延迟处理队列
  163.         User f = ModelRegistry.getUser(packet.normalHeader.sender);
  164.         boolean iAmHisStranger = packet.header.type == QQ.QQ_RECV_IM_STRANGER;
  165.         boolean noSuchUser = f == null || f.group.isCluster();
  166.         // boolean heIsMyStranger = noSuchUser || f.group.isStranger();
  167.         boolean heIsMyBlacklist = f != null && f.group.isBlackList();
  168.         if (heIsMyBlacklist || noSuchUser && iAmHisStranger) {
  169.             return;
  170.         }
  171.         // if (heIsMyStranger) return;
  172.         UIHelper uihelper = main.getUIHelper();
  173.         // 如果不存在这个用户,添加到陌生人
  174.         if (noSuchUser) {
  175.             f = new User();
  176.             f.qq = packet.normalHeader.sender;
  177.             f.nick = String.valueOf(f.qq);
  178.             f.displayName = f.nick;
  179.             uihelper.addUser(f, GroupType.STRANGER_GROUP);
  180.             main.getUIHelper().refreshGroup();
  181.             main.getClient().user_GetInfo(f.qq);
  182.         }
  183.         uihelper.appendMessage(f, packet.normalIM, packet.normalHeader);
  184.         // 推入队列
  185.         mq.putMessage(packet);
  186.     }
  187. }

 

有了这两个基础的类之后我们就可以对QQ消息事件进行处理了。QQ消息方面的事件很多,这里我们只处理一下普通消息的事件

  1. protected void OnQQEvent(QQEvent e) {
  2.     switch (e.type) {
  3.         case QQEvent.IM_RECEIVED:
  4.         processReceiveNormalIM(e);
  5.         break;
  6.     }
  7. }
  8. /**
  9. * 处理收到消息事件
  10. * @param e
  11. *       QQEvent
  12. */
  13. private void processReceiveNormalIM(QQEvent e) {
  14.     // 得到包,推入消息队列
  15.     ReceiveIMPacket packet = (ReceiveIMPacket)e.getSource();
  16.     main.getMessageHelper().putNormalIM(packet);
  17. }
 
代码在这里,请点击下载http://download.csdn.net/source/698372
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值