简单的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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void invitationDeclined(String invitee, String reason) {System.out.println(invitee + " reject invitation, reason is " + reason);}});//邀请用户加入聊天室muc.invite("test3@pc2010102716", "大家来谈谈人生");//监听邀请加入聊天室请求MultiUserChat.addInvitationListener(connection, new InvitationListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void voiceRevoked() {super.voiceRevoked();System.out.println("你被禁言了!");} @Overridepublic void voiceGranted() {super.voiceGranted();System.out.println("你被批准发言了!");} @Overridepublic void membershipGranted() {super.membershipGranted();System.out.println("你被赋予了Member权限");} @Overridepublic void membershipRevoked() {super.membershipRevoked();System.out.println("你被解除了Member权限");} @Overridepublic void adminGranted() {super.adminGranted();System.out.println("你被赋予了管理员权限");} @Overridepublic void adminRevoked() {super.adminRevoked();System.out.println("你被解除了管理员权限");}//......});//房主(Owner)批准test3发言权muc.grantVoice("test3@pc2010102716"); //监听他人状态变更muc.addParticipantStatusListener(new DefaultParticipantStatusListener() { @Overridepublic void voiceGranted(String participant) {super.voiceGranted(participant);System.out.println(participant + "被批准发言了!");} @Overridepublic void voiceRevoked(String participant) {super.voiceRevoked(participant);System.out.println(participant + "被禁言了!");} @Overridepublic void membershipRevoked(String participant) {super.membershipRevoked(participant);} @Overridepublic void adminGranted(String participant) {super.adminGranted(participant);} @Overridepublic 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 # Itemspm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());// Service Discovery # Infopm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Service Discovery # Itemspm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());// Service Discovery # Infopm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); //Offline Message Requestspm.addIQProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); //Offline Message Indicatorpm.addExtensionProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); //vCardpm.addIQProvider("vCard","vcard-temp", new VCardProvider()); //FileTransferpm.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 Formspm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider());//Htmlpm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); //Ad-Hoc Commandpm.addIQProvider("command", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider()); // Chat StateChatStateExtension.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,Ownerpm.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系统同理.