Android中基于XMPP协议实现IM聊天程序与多人聊天室

简单的IM聊天程序
由于项目需要做一个基于XMPP协议的Android通讯软件。故开始研究XMPP。
XMPP协议采用的是客户端-服务器架构,所有从一个客户端发到另一个客户端的消息和数据都必须经过XMPP服务器转发,而且支持服务器间DNS的路由,也就是说可以构建服务器集群,使不同的

服务器下的客户端也可以通信,XMPP的前身是一个开源组织制定的网络通信协议——Jabber,XMPP的核心是在网络上分片段发送XML流的协议,这个协议是XMPP的即时通讯指令的传递手段。
      为了防止服务器间发送的数据被篡改或偷听,XMPP服务器通信引入了TLS机制,使用TLS机制能实现数据的加密,从而保证了在数据传输过程种数据的安全。
      一个XMPP实体的地址称为Jabber Identifier或JID,作用类似于IP地址。一个合法的JID包括节点名,域名资源名,其格式为:jid=[node'@']domain['/'resource]

XMPP协议的命名空间:

  • jabber:iq:private   -- 私有数据存储,用于本地用户私人设置信息,比如用户备注等。
  • jabber:iq:conference  -- 一般会议,用于多个用户之间的信息共享
  • jabber:x:encrypted -- 加密的消息,用于发送加密消息
  • jabber:x:expire  -- 消息终止
  • jabber:iq:time  -- 客户端时间
  • jabber:iq:auth  -- 简单用户认证,一般用于服务器之间或者服务器和客户端之间的认证
  • jabber:x:roster  -- 内部花名册
  • jabber:x:signed  -- 标记的在线状态
  • jabber:iq:search -- 用户数据库查询,用于向服务器发送查询请求
  • jabber:iq:register -- 注册请求,用于用户注册相关信息
  • jabber:x:iq:roster -- 花名册管理
  • jabber:x:conference -- 会议邀请,用于向参加会议用户发送开会通知
  • jabber:x:event  -- 消息事件
  • vcard-temp  -- 临时的vCard,用于设置用户的头像以及昵称等

在网上找了下,有开源的项目BEEM,开源的用于android的xmpp框架asmack,asmack是smack的android版本。现在开始学习smack

。Xmpp就是神马东西,就不废话了。首先在网上下一个Openfire和Spack,不知道这两个是什么东西,就直接google吧。安装openfire需要mysql的支持,当然,oracle,sqlserver肯定是可以的。还是先上图吧:

Openfire + Spark + MyXMPPP

import java.io.InputStreamReader;
import java.util.Collection; 

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PrivacyListManager;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence; 

public class TestSmack {
  public static void main(String[] args) {XMPPConnection.DEBUG_ENABLED = true;
    //我的电脑IP:10.16.25.90
    final ConnectionConfiguration connectionConfig = new ConnectionConfiguration("10.16.25.91", 5222, "");
    connectionConfig.setSASLAuthenticationEnabled(false);
        try { 

          XMPPConnection connection = new XMPPConnection(connectionConfig);
          connection.connect();//连接
          connection.login("test", "test");//登陆
          System.out.println(connection.getUser());
          ChatManager chatmanager = connection.getChatManager(); 

          //新建一个会话
          Chat newChat = chatmanager.createChat("test3@pc2010102716", new MessageListener() {
            public void processMessage(Chat chat, Message message) {
              System.out.println("Received from 【" + message.getFrom() + "】 message: " + message.getBody());
            }
          }); 

          // 监听被动接收消息,或广播消息监听器
          chatmanager.addChatListener(new ChatManagerListener() {
            @Override
            public void chatCreated(Chat chat, boolean createdLocally) {
              chat.addMessageListener(new MessageListener() {
                @Override
                public void processMessage(Chat chat, Message message) {
                  System.out.println("Received from 【" + message.getFrom() + "】 message: " + message.getBody());
                } 

              });
            }
          });
          //发送消息
          newChat.sendMessage("我是菜鸟"); 

          //获取花名册
          Roster roster = connection.getRoster();
          Collection<RosterEntry> entries = roster.getEntries();
          for(RosterEntry entry : entries) {
            System.out.print(entry.getName() + " - " + entry.getUser() + " - " + entry.getType() + " - " + entry.getGroups().size());
            Presence presence = roster.getPresence(entry.getUser());
            System.out.println(" - " + presence.getStatus() +" - "+ presence.getFrom());
          } 

          //添加花名册监听器,监听好友状态的改变。
          roster.addRosterListener(new RosterListener() { 

            @Override
            public void entriesAdded(Collection<String> addresses) {
              System.out.println("entriesAdded");
            } 

            @Override
            public void entriesUpdated(Collection<String> addresses) {
              System.out.println("entriesUpdated");
            } 

            @Override
            public void entriesDeleted(Collection<String> addresses) {
              System.out.println("entriesDeleted");
            } 

            @Override
            public void presenceChanged(Presence presence) {
              System.out.println("presenceChanged - >" + presence.getStatus());
            } 

          }); 

          //创建组
//         /RosterGroup group = roster.createGroup("大学");
//         for(RosterEntry entry : entries) {
//           group.addEntry(entry);
//         }
          for(RosterGroup g : roster.getGroups()) {
            for(RosterEntry entry : g.getEntries()) {
              System.out.println("Group " +g.getName() +" >> " + entry.getName() + " - " + entry.getUser() + " - " + entry.getType() + " - " + entry.getGroups().size());
            }
          } 

          //发送消息
          BufferedReader cmdIn = new BufferedReader(new InputStreamReader(System.in));
          while(true) {
           try {
             String cmd = cmdIn.readLine();
             if("!q".equalsIgnoreCase(cmd)) {
               break;
             }
             newChat.sendMessage(cmd);
           }catch(Exception ex) {
           }
          }
          connection.disconnect();
          System.exit(0);
        } catch (Exception e) {
          e.printStackTrace();
        }
  }
}

以上代码如果在一般的Java Project上运行需要加入smack.jar 和klmx2.jar,如果是Android Project,基本代码不需改变只需将其放入onCreate(...)方法下即可,需要加入asmack.jar包.

1、ConnectionConfiguration
 作为用于与XMPP服务建立连接的配置。它能配置;连接是否使用TLS,SASL加密。
 包含内嵌类:ConnectionConfiguration.SecurityMode

2、XMPPConnection.
 XMPPConnection这个类用来连接XMPP服务.
 可以使用connect()方法建立与服务器的连接。disconnect()方法断开与服务器的连接.
 在创建连接前可以使用XMPPConnection.DEBUG_ENABLED = true; 使开发过程中可以弹出一个GUI窗口,用于显示我们的连接与发送Packet的信息。

3、ChatManager
 用于监控当前所有chat。可以使用createChat(String userJID, MessageListener listener)创建一个聊天。
 
4、Chat
 Chat用于监控两个用户间的一系列message。使用addMessageListener(MessageListener listener)当有任何消息到达时将会触发listener的processMessage(Chat chat, Message message)
方法.
 我们可以使用sendMessage()发送消息,这个方法有两个重载方法,一种类类型的参数时String类型,另一种则是传入Message对象(后面介绍)。
 那么有这样一种情况,当别人主动跟我们建立连接发送消息,或者系统发送消息时我们怎么才能接收消息呢?
    我现在是这样操作的:

 chatmanager.addChatListener(new ChatManagerListener() {
 @Override
 public void chatCreated(Chat chat, boolean createdLocally) {
  chat.addMessageListener(new MessageListener() {
  @Override
  public void processMessage(Chat chat, Message message) {
   System.out.println("Received message: " + message.getBody());
  }

  });
 }
 });

5、Message

  • Message用于表示一个消息包(可以用调试工具看到发送包和接收包的具体内容)。它有以下多种类型。
  • Message.Type.NORMAL -- (默认)文本消息(比如邮件)
  • Message.Type.CHAT -- 典型的短消息,如QQ聊天的一行一行显示的消息
  • Message.Type.GROUP_CHAT -- 群聊消息
  • Message.Type.HEADLINE -- 滚动显示的消息
  • Message.TYPE.ERROR -- 错误的消息
  • Message有两个内部类:
  • Message.Body -- 表示消息体
  • Message.Type -- 表示消息类型

6、Roster
 表示存储了很多RosterEntry的一个花名册.为了易于管理,花名册的项被分贝到了各个group中.
 当建立与XMPP服务的连接后可以使用connection.getRoster()获取Roster对象。
 别的用户可以使用一个订阅请求(相当于QQ加好友)尝试订阅目的用户。可以使用枚举类型Roster.SubscriptionMode的值处理这些请求:
 accept_all: 接收所有订阅请求
 reject_all:拒绝所有订阅请求
 manual:  手工处理订阅请求
 
 创建组:RosterGroup group = roster.createGroup("大学");
 向组中添加RosterEntry对象: group.addEntry(entry);
 
7、RosterEntry
 表示Roster(花名册)中的每条记录.它包含了用户的JID,用户名,或用户分配的昵称.
 
8、RosterGroup
 表示RosterEntry的组。可以使用addEntry(RosterEntry entry)添加。contains(String user) 判断某用户是否在组中.当然removeEntry(RosterEntry entry)就是从组中移除了。getEntries()

获取所有RosterEntry.
 
9、Presence
 表示XMPP状态的packet。每个presence packet都有一个状态。用枚举类型Presence.Type的值表示:
 available -- (默认)用户空闲状态
 unavailable -- 用户没空看消息
 subscribe -- 请求订阅别人,即请求加对方为好友
 subscribed -- 统一被别人订阅,也就是确认被对方加为好友
 unsubscribe -- 他取消订阅别人,请求删除某好友
 unsubscribed -- 拒绝被别人订阅,即拒绝对放的添加请求
 error -- 当前状态packet有错误
 内嵌两个枚举类型:Presence.Mode和Presence.Type.
 可以使用setStatus自定义用户当前的状态(像QQ一样的)

MultiUserChat聊天室

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;  

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.Form;
import org.jivesoftware.smackx.FormField;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.muc.DefaultParticipantStatusListener;
import org.jivesoftware.smackx.muc.DefaultUserStatusListener;
import org.jivesoftware.smackx.muc.DiscussionHistory;
import org.jivesoftware.smackx.muc.HostedRoom;
import org.jivesoftware.smackx.muc.InvitationListener;
import org.jivesoftware.smackx.muc.InvitationRejectionListener;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.RoomInfo;
import org.jivesoftware.smackx.muc.SubjectUpdatedListener;
import org.jivesoftware.smackx.packet.ChatStateExtension;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.jivesoftware.smackx.packet.OfflineMessageInfo;
import org.jivesoftware.smackx.packet.OfflineMessageRequest;
import org.jivesoftware.smackx.provider.AdHocCommandDataProvider;
import org.jivesoftware.smackx.provider.BytestreamsProvider;
import org.jivesoftware.smackx.provider.DataFormProvider;
import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverItemsProvider;
import org.jivesoftware.smackx.provider.IBBProviders;
import org.jivesoftware.smackx.provider.MUCAdminProvider;
import org.jivesoftware.smackx.provider.MUCOwnerProvider;
import org.jivesoftware.smackx.provider.MUCUserProvider;
import org.jivesoftware.smackx.provider.StreamInitiationProvider;
import org.jivesoftware.smackx.provider.VCardProvider;
import org.jivesoftware.smackx.provider.XHTMLExtensionProvider;  

public class TestSmack2 {
  public static void main(String[] args) {XMPPConnection.DEBUG_ENABLED = true;
    final ConnectionConfiguration connectionConfig = new ConnectionConfiguration("PC2010102716", 5222, "");
    connectionConfig.setSASLAuthenticationEnabled(false);
    ProviderManager pm = ProviderManager.getInstance();
    configure(pm);
    try {
      XMPPConnection connection = new XMPPConnection(connectionConfig);
      connection.connect();//连接
      initFeatures(connection);
      connection.login("test", "test");//登陆
      //聊天室
      //MultiUserChat multiUserChat = new MultiUserChat(connection, new InvitationListener() {});
       //查找服务
      System.out.println(connection.getServiceName());
      List<String> col = getConferenceServices(connection.getServiceName(), connection);
      for (Object aCol : col) {
        String service = (String) aCol;
         //查询服务器上的聊天室
        Collection<HostedRoom> rooms = MultiUserChat.getHostedRooms(connection, service);
        for(HostedRoom room : rooms) {
          //查看Room消息
          System.out.println(room.getName() + " - " +room.getJid());
          RoomInfo roomInfo = MultiUserChat.getRoomInfo(connection, room.getJid());
          if(roomInfo != null) {
            System.out.println(roomInfo.getOccupantsCount() + " : " + roomInfo.getSubject());
          }
         }
      }  

      /*---创建默认配置的聊天室 ---
      先看看官方的文档:
      Creates a new multi user chat with the specified connection and room name. Note: no
         * information is sent to or received from the server until you attempt to
         * {@link #join(String) join} the chat room. On some server implementations,
         * the room will not be created until the first person joins it
         * 最重要一句:直到用户调用join方法的时候聊天室才会被创建
         */
      MultiUserChat muc = new MultiUserChat(connection, "instant@conference.pc2010102716");
       muc.create("user1");
      muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));  

      //----创建手动配置聊天室----
      muc = new MultiUserChat(connection, "reserved4@conference.pc2010102716");  

      //销毁聊天室
      //muc.destroy("Test", null);
      muc.create("user2");
      //获取聊天室的配置表单
      Form form = muc.getConfigurationForm();
      //根据原始表单创建一个要提交的新表单
      Form submitForm = form.createAnswerForm();
      //向提交的表单添加默认答复
      for(Iterator<FormField> fields = form.getFields(); fields.hasNext();) {
        FormField field = (FormField) fields.next();
        if(!FormField.TYPE_HIDDEN.equals(field.getType()) && field.getVariable() != null) {
          submitForm.setDefaultAnswer(field.getVariable());
        }
      }
      //重新设置聊天室名称
      submitForm.setAnswer("muc#roomconfig_roomname", "Reserved4 Room");
      //设置聊天室的新拥有者
      List<String> owners = new ArrayList<String>();
      owners.add("test@pc2010102716");
      submitForm.setAnswer("muc#roomconfig_roomowners", owners);
      //设置密码
      submitForm.setAnswer("muc#roomconfig_passwordprotectedroom", true);
      submitForm.setAnswer("muc#roomconfig_roomsecret", "reserved");
      //设置描述
      submitForm.setAnswer("muc#roomconfig_roomdesc", "新创建的reserved聊天室");
      //设置聊天室是持久聊天室,即将要被保存下来
      //submitForm.setAnswer("muc#roomconfig_persistentroom", true);
      //发送已完成的表单到服务器配置聊天室
      muc.sendConfigurationForm(submitForm);  

      //加入聊天室(使用昵称喝醉的毛毛虫 ,使用密码ddd)
      muc = new MultiUserChat(connection, "ddd@conference.pc2010102716");
      muc.join("喝醉的毛毛虫", "ddd");  

      //监听消息
      muc.addMessageListener(new PacketListener() {
        @Override
        public void processPacket(Packet packet) {
          Message message = (Message) packet;
          System.out.println(message.getFrom() + " : " + message.getBody());;
        }
      });  

      //muc = new MultiUserChat(connection, "ddd@conference.pc2010102716");
      //muc.join("喝醉的毛毛虫", "ddd");  

      //加入聊天室(使用昵称喝醉的毛毛虫 ,使用密码ddd)并且获取聊天室里最后5条信息,
      //注:addMessageListener监听器必须在此join方法之前,否则无法监听到需要的5条消息
      muc = new MultiUserChat(connection, "ddd@conference.pc2010102716");
      DiscussionHistory history = new DiscussionHistory();
      history.setMaxStanzas(5);
      muc.join("喝醉的毛毛虫", "ddd", history, SmackConfiguration.getPacketReplyTimeout());  

      //监听拒绝加入聊天室的用户
      muc.addInvitationRejectionListener(new InvitationRejectionListener() {
        @Override
        public void invitationDeclined(String invitee, String reason) {
        System.out.println(invitee + " reject invitation, reason is " + reason);
        }
      });
      //邀请用户加入聊天室
      muc.invite("test3@pc2010102716", "大家来谈谈人生");
      //监听邀请加入聊天室请求
      MultiUserChat.addInvitationListener(connection, new InvitationListener() {
        @Override
        public void invitationReceived(XMPPConnection conn, String room, String inviter, String reason, String password, Message message) {
        //例:直接拒绝邀请
        MultiUserChat.decline(conn, room, inviter, "你丫很闲啊!");
        }
      });  

      //根据roomJID获取聊天室信息
      RoomInfo roomInfo = MultiUserChat.getRoomInfo(connection, "ddd@conference.pc2010102716");
       System.out.println(roomInfo.getRoom() + "-" + roomInfo.getSubject() + "-" + roomInfo.getOccupantsCount()); 

      //判断用户是否支持Multi-User聊天协议
      //注:需要加上资源标识符
      boolean supports = MultiUserChat.isServiceEnabled(connection, "test3@pc2010102716/spark");
       //获取某用户所加入的聊天室
      if(supports) {
        Iterator<String> joinedRooms = MultiUserChat.getJoinedRooms(connection, "test3@pc2010102716/spark");
        while(joinedRooms.hasNext()) {
          System.out.println("test3 has joined Room " + joinedRooms.next());
        }
      }  

      //与聊天室用户私聊
      Chat chat = muc.createPrivateChat("ddd@conference.pc2010102716/飞鸟", new MessageListener() {
         @Override
         public void processMessage(Chat chat, Message message) {
           System.out.println("Private Chat: Received message from " + message.getFrom() + "-" + message.getBody());
         }
      });
      chat.sendMessage("今天不用加班吧?");  

      //改变聊天室主题
      muc.addSubjectUpdatedListener(new SubjectUpdatedListener() {
        @Override
        public void subjectUpdated(String subject, String from) {
          System.out.println("Subject updated to " + subject +" by " + from);
        }
      });
      //muc.changeSubject("New Subject11");  

      /*一个成员可能有四种角色:
      1:主持者(Moderator) (权限最大的角色,管理其他成员在聊天室中的角色
      2:参与者(Participant
      3:游客 (Visitor) (不能向所有成员发送消息)
      4:无(没有角色)(NONE)
      */  

      /*聊天室用户可以有5种从属关系
       * 1、所有者 Owner
       * 2、管理员 Admin
       * 3、成员 Member
       * 4、被驱逐者 Outcast
       * 5、无(不存在从属关系) None
       */  

      //配置聊天室为Moderated聊天室
      form = muc.getConfigurationForm();
      Form answerForm = form.createAnswerForm();
      answerForm.setAnswer("muc#roomconfig_moderatedroom", "1");
      muc.sendConfigurationForm(answerForm);  

      //监听自己的状态变更和事件
      muc.addUserStatusListener(new DefaultUserStatusListener() {
        @Override
        public void voiceRevoked() {
          super.voiceRevoked();
          System.out.println("你被禁言了!");
        }  

        @Override
        public void voiceGranted() {
          super.voiceGranted();
          System.out.println("你被批准发言了!");
        }  

        @Override
        public void membershipGranted() {
          super.membershipGranted();
          System.out.println("你被赋予了Member权限");
        }  

        @Override
        public void membershipRevoked() {
          super.membershipRevoked();
          System.out.println("你被解除了Member权限");
        }  

        @Override
        public void adminGranted() {
          super.adminGranted();
          System.out.println("你被赋予了管理员权限");
        }  

        @Override
        public void adminRevoked() {
        super.adminRevoked();
        System.out.println("你被解除了管理员权限");
        }
      //......
      });
      //房主(Owner)批准test3发言权
      muc.grantVoice("test3@pc2010102716");  

      //监听他人状态变更
      muc.addParticipantStatusListener(new DefaultParticipantStatusListener() {  

        @Override
        public void voiceGranted(String participant) {
          super.voiceGranted(participant);
        System.out.println(participant + "被批准发言了!");
        }  

        @Override
        public void voiceRevoked(String participant) {
          super.voiceRevoked(participant);
          System.out.println(participant + "被禁言了!");
        }  

        @Override
        public void membershipRevoked(String participant) {
          super.membershipRevoked(participant);
        }  

        @Override
        public void adminGranted(String participant) {
          super.adminGranted(participant);
        }  

        @Override
        public void adminRevoked(String participant) {
          super.adminRevoked(participant);
        }  

      });  

      //房主(Owner)批准test3管理员特权
      muc.grantAdmin("test3@pc2010102716");  

      //发送消息
      BufferedReader cmdIn = new BufferedReader(new InputStreamReader(System.in));
      while(true) {
        try {
           String cmd = cmdIn.readLine();
           if("!q".equalsIgnoreCase(cmd)) {
             break;
           }
         }catch(Exception ex) {
         }
      }
      connection.disconnect();
      System.exit(0);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }  

  public static List<String> getConferenceServices(String server, XMPPConnection connection) throws Exception {
   List<String> answer = new ArrayList<String>();
      ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
       DiscoverItems items = discoManager.discoverItems(server);
      for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) {
         DiscoverItems.Item item = (DiscoverItems.Item)it.next();
        if (item.getEntityID().startsWith("conference") || item.getEntityID().startsWith("private")) {
           answer.add(item.getEntityID());
        }
        else {
          try {
            DiscoverInfo info = discoManager.discoverInfo(item.getEntityID());
             if (info.containsFeature("http://jabber.org/protocol/muc")) {
               answer.add(item.getEntityID());
            }
          }
          catch (XMPPException e) {
          }
        }
      }
      return answer;
    }  

  private static void configure(ProviderManager pm) {
    // Service Discovery # Items
    pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
     // Service Discovery # Info
    pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); 

    // Service Discovery # Items
    pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
     // Service Discovery # Info
    pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); 

    //Offline Message Requests
    pm.addIQProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider());
        //Offline Message Indicator
        pm.addExtensionProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider());     

    //vCard
    pm.addIQProvider("vCard","vcard-temp", new VCardProvider());  

    //FileTransfer
    pm.addIQProvider("si","http://jabber.org/protocol/si", new StreamInitiationProvider());
        pm.addIQProvider("query","http://jabber.org/protocol/bytestreams", new BytestreamsProvider());
        pm.addIQProvider("open","http://jabber.org/protocol/ibb", new IBBProviders.Open());
        pm.addIQProvider("close","http://jabber.org/protocol/ibb", new IBBProviders.Close());
        pm.addExtensionProvider("data","http://jabber.org/protocol/ibb", new IBBProviders.Data());
        //Data Forms
      pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider());
      //Html
      pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider());
      //Ad-Hoc Command
      pm.addIQProvider("command", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider());
    // Chat State
    ChatStateExtension.Provider chatState = new ChatStateExtension.Provider();
    pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", chatState);
    pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates",
      chatState);
    pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", chatState);
    pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", chatState);
    pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", chatState);
    //MUC User,Admin,Owner
    pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider());
    pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new MUCAdminProvider());
    pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider());
  }  

  private static void initFeatures(XMPPConnection xmppConnection) {
    ServiceDiscoveryManager.setIdentityName("Android_IM");
    ServiceDiscoveryManager.setIdentityType("phone");
    ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(xmppConnection);
     if(sdm == null) {
    sdm = new ServiceDiscoveryManager(xmppConnection);
    }
    sdm.addFeature("http://jabber.org/protocol/disco#info");
    sdm.addFeature("http://jabber.org/protocol/caps");
    sdm.addFeature("urn:xmpp:avatar:metadata");
    sdm.addFeature("urn:xmpp:avatar:metadata+notify");
    sdm.addFeature("urn:xmpp:avatar:data");
    sdm.addFeature("http://jabber.org/protocol/nick");
    sdm.addFeature("http://jabber.org/protocol/nick+notify");
    sdm.addFeature("http://jabber.org/protocol/xhtml-im");
    sdm.addFeature("http://jabber.org/protocol/muc");
    sdm.addFeature("http://jabber.org/protocol/commands");
    sdm.addFeature("http://jabber.org/protocol/si/profile/file-transfer");
    sdm.addFeature("http://jabber.org/protocol/si");
    sdm.addFeature("http://jabber.org/protocol/bytestreams");
    sdm.addFeature("http://jabber.org/protocol/ibb");
    sdm.addFeature("http://jabber.org/protocol/feature-neg");
    sdm.addFeature("jabber:iq:privacy");
  }  

}

上面有两张Spark客户端的聊天室列表占有者一列不同的原因:当使用以下代码获取时不能获取occupantsCount和subject的值:

System.out.println(roomInfo.getOccupantsCount() + " : " + roomInfo.getSubject());

这是由于Openfire端有个bug(暂且这样说吧,我不知为什么Openfire这样做).首先修改Smack的一个bug,修改RoomInfo类的RoomInfo(DiscoverInfo info) 方法:

Iterator<String> values = form.getField("muc#roominfo_subject").getValues();
if (values.hasNext()) {
  this.subject = values.next();
}
else {
  this.subject = "";
}

改为:

final FormField subjField = form.getField("muc#roominfo_subject");
this.subject = subjField == null ? "" : subjField.getValues().next();

修改Openfire的源码,org/jivesoftware/openfire/muc/spi/MultiUserChatServiceImpl类的public DataForm getExtendedInfo(String name, String node, JID senderJID) 方法:

      /*final FormField fieldOcc = dataForm.addField(); */
      fieldSubj.setVariable("muc#roominfo_occupants");
      fieldSubj.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.occupants"));
      fieldSubj.addValue(Integer.toString(room.getOccupantsCount()));

改为:

final FormField fieldOccu= dataForm.addField(); 
fieldOccu.setVariable("muc#roominfo_occupants"); 
fieldOccu.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.occupants")); 
fieldOccu.addValue(Integer.toString(room.getOccupantsCount()));

使用asmack开发基于Android的IM系统同理.

(0)

相关推荐

  • Android使用多线程进行网络聊天室通信

    TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路.一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信了.Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端通信接口,并通过Socket产生IO流来进行网络通信. 下面的程序Demo是实现一个简单的C/S聊天室的应用,每个客户端该包含两条线程:一条负责生成主界面,响应用户动作,并将用户输入的数据写入Socket对应的输出流中:另一条

  • Android编写简单的聊天室应用

    最近写了一个简单的聊天室应用,可以发送表情,更改头像这些功能.主要技术点就是怎样把表情图片放到textview等Ui控件中展示.这里废话不多说,下面是效果图: 这里主要讲下怎样把文本替换到表情,先说下思路,首先我们的图片是保存在本地资源目录drawable中而所有的资源文件都是R这个类来管理,所以我们可以利用正则表达式找出图片id包装成ImageSpan然后把ImageSpan放到SpannableString中,最后把SpannableString放入edittext中,下面是源码: pack

  • android socket聊天室功能实现

    前提概要 笔者很久之前其实就已经学习过了socket,当然也是用socket做过了聊天室,但是觉得此知识点比较一般,并无特别难的技术点,于是也并未深究. 然而近期一个项目中对socket的使用却让笔者感觉socket强大无比,可以实现诸多功能. 个人Socket体验 项目主要有关智能家居,需要实现多台手机同时对灯进行操作(开或者关),主要需要实现以下几点: 1.进入界面时获取所有灯的状态. 2.一台手机改变了灯的状态,其他的手机上可以有所显示. 3.硬件上改变了灯的状态(手动开关灯),所有手机上

  • Android 基于Socket的聊天室实例

    Socket是TCP/IP协议上的一种通信,在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路.一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信. Client A  发信息给 Client B ,  A的信息首先发送信息到服务器Server ,Server接受到信息后再把A的信息广播发送给所有的Clients 首先我们要在服务器建立一个ServerSocket ,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待

  • Android中基于XMPP协议实现IM聊天程序与多人聊天室

    简单的IM聊天程序 由于项目需要做一个基于XMPP协议的Android通讯软件.故开始研究XMPP. XMPP协议采用的是客户端-服务器架构,所有从一个客户端发到另一个客户端的消息和数据都必须经过XMPP服务器转发,而且支持服务器间DNS的路由,也就是说可以构建服务器集群,使不同的 服务器下的客户端也可以通信,XMPP的前身是一个开源组织制定的网络通信协议--Jabber,XMPP的核心是在网络上分片段发送XML流的协议,这个协议是XMPP的即时通讯指令的传递手段.       为了防止服务器间

  • Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能示例

    本文实例讲述了Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能.分享给大家供大家参考,具体如下: [吐槽] 网上的代码害死人,看着都写的言之凿凿,可运行就是有问题. 有些爱好代码.喜欢收藏代码的朋友,看到别人的代码就粘贴复制过来.可是起码你也试试运行看啊大哥 [正文] 昨日修改运行了UDP协议的C/S聊天程序,可是TCP协议的怎么都不行.各种试,各种坑. 做了下面几个修改后,终于可以了: 1.对发送.接收的信息,分别进行编码和解码 2.客户端的第10行bind改为c

  • Android 中基于TabLayout+ViewPager实现标签卡效果

    代码已经上传至Github:https://github.com/YanYoJun/ViewPagerDemo 先看效果 1.布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.andro

  • 对python中基于tcp协议的通信(数据传输)实例讲解

    阅读目录 tcp协议:流式协议(以数据流的形式通信传输).安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层.数据链路层.物理层.可以说很多安全数据的传输通信都是基于tcp协议进行的. 为了让tcp通信更加方便需要引入一个socket模块(将网络层.数据链路层.物理层封装的模块),我们只要调用模块中的相关接口就能实现传输层下面的繁琐操作. 简单的tcp协议通信模板:(需要一个服务端和一个客户端) 服务

  • Golang中基于HTTP协议的网络服务

    目录 一.HTTP协议的网络服务 1.1 使用http.Get函数访问HTTP协议的网络服务 1.2 使用缺省客户端DefaultClient(类型为*http.Client ) 1.3 使用http.Client访问HTTP协议的网络服务 二.http.Client中的Transport字段 (1)http.Transport类型中的DialContext字段 (2)http.Transport类型中的其它字段 三.为什么会出现空闲的连接 3.1 空闲连接的产生 3.2 杜绝空闲连接的产生 四

  • Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程

    小程序官方流程图如下,官方地址 : https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html : 本文是对接微信小程序自定义登录的一个完整例子实现 ,技术栈为 : SpringBoot+Shiro+JWT+JPA+Redis. 如果对该例子比较感兴趣或者觉得言语表达比较啰嗦,可查看完整的项目地址 : https://github.com/EalenXie/shiro-jwt-applet

  • Android中利用xml文件布局修改Helloworld程序

    Android环境布置完毕,直接就是一个Helloworld程序,详情请看<利用adt-bundle轻松搭建Android开发环境与Hello world(Linux)>这使得很多人难以理清整个Android项目的基本结构.其实安卓项目,与其它Java工程的项目,SSH.Servlet等,都是采取Java与XML文件联合的方式,形成一个工程的. 有几个文件是需要熟知的.如下图: MainActivity.java是整个安卓工程的入口, 其中,里面的protected void onCreate

  • 使用C语言编写基于TCP协议的Socket通讯程序实例分享

    tcp客户端示例 #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h>

  • Java基于UDP协议实现简单的聊天室程序

    最近比较闲,一直在抽空回顾一些Java方面的技术应用. 今天没什么事做,基于UDP协议,写了一个非常简单的聊天室程序. 现在的工作,很少用到socket,也算是对Java网络编程方面的一个简单回忆. 先看一下效果: 实现的效果可以说是非常非常简单,但还是可以简单的看到一个实现原理.  "聊天室001"的用户,小红和小绿相互聊了两句,"聊天室002"的小黑无人理会,在一旁寂寞着. 看一下代码实现: 1.首先是消息服务器的实现,功能很简单: •将客户端的信息(进入了哪一

  • 详解Android中Intent对象与Intent Filter过滤匹配过程

    如果对Intent不是特别了解,可以参见博文<详解Android中Intent的使用方法>,该文对本文要使用的action.category以及data都进行了详细介绍.如果想了解在开发中常见Intent的使用,可以参见<Android中Intent习惯用法>. 本文内容有点长,希望大家可以耐心读完. 本文在描述组件在manifest中注册的Intent Filter过滤器时,统一用intent-filter表示. 一.概述 我们知道,Intent是分两种的:显式Intent和隐式

随机推荐