Android中基于XMPP協(xié)議實現(xiàn)IM聊天程序與多人聊天室
簡單的IM聊天程序
由于項目需要做一個基于XMPP協(xié)議的Android通訊軟件。故開始研究XMPP。
XMPP協(xié)議采用的是客戶端-服務(wù)器架構(gòu),所有從一個客戶端發(fā)到另一個客戶端的消息和數(shù)據(jù)都必須經(jīng)過XMPP服務(wù)器轉(zhuǎn)發(fā),而且支持服務(wù)器間DNS的路由,也就是說可以構(gòu)建服務(wù)器集群,使不同的
服務(wù)器下的客戶端也可以通信,XMPP的前身是一個開源組織制定的網(wǎng)絡(luò)通信協(xié)議——Jabber,XMPP的核心是在網(wǎng)絡(luò)上分片段發(fā)送XML流的協(xié)議,這個協(xié)議是XMPP的即時通訊指令的傳遞手段。
為了防止服務(wù)器間發(fā)送的數(shù)據(jù)被篡改或偷聽,XMPP服務(wù)器通信引入了TLS機(jī)制,使用TLS機(jī)制能實現(xiàn)數(shù)據(jù)的加密,從而保證了在數(shù)據(jù)傳輸過程種數(shù)據(jù)的安全。
一個XMPP實體的地址稱為Jabber Identifier或JID,作用類似于IP地址。一個合法的JID包括節(jié)點(diǎn)名,域名資源名,其格式為:jid=[node'@']domain['/'resource]
XMPP協(xié)議的命名空間:
- jabber:iq:private -- 私有數(shù)據(jù)存儲,用于本地用戶私人設(shè)置信息,比如用戶備注等。
- jabber:iq:conference -- 一般會議,用于多個用戶之間的信息共享
- jabber:x:encrypted -- 加密的消息,用于發(fā)送加密消息
- jabber:x:expire -- 消息終止
- jabber:iq:time -- 客戶端時間
- jabber:iq:auth -- 簡單用戶認(rèn)證,一般用于服務(wù)器之間或者服務(wù)器和客戶端之間的認(rèn)證
- jabber:x:roster -- 內(nèi)部花名冊
- jabber:x:signed -- 標(biāo)記的在線狀態(tài)
- jabber:iq:search -- 用戶數(shù)據(jù)庫查詢,用于向服務(wù)器發(fā)送查詢請求
- jabber:iq:register -- 注冊請求,用于用戶注冊相關(guān)信息
- jabber:x:iq:roster -- 花名冊管理
- jabber:x:conference -- 會議邀請,用于向參加會議用戶發(fā)送開會通知
- jabber:x:event -- 消息事件
- vcard-temp -- 臨時的vCard,用于設(shè)置用戶的頭像以及昵稱等
在網(wǎng)上找了下,有開源的項目BEEM,開源的用于android的xmpp框架asmack,asmack是smack的android版本?,F(xiàn)在開始學(xué)習(xí)smack
。Xmpp就是神馬東西,就不廢話了。首先在網(wǎng)上下一個Openfire和Spack,不知道這兩個是什么東西,就直接google吧。安裝openfire需要mysql的支持,當(dāng)然,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());
}
});
// 監(jiān)聽被動接收消息,或廣播消息監(jiān)聽器
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());
}
});
}
});
//發(fā)送消息
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());
}
//添加花名冊監(jiān)聽器,監(jiān)聽好友狀態(tài)的改變。
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());
}
});
//創(chuàng)建組
// /RosterGroup group = roster.createGroup("大學(xué)");
// 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());
}
}
//發(fā)送消息
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上運(yùn)行需要加入smack.jar 和klmx2.jar,如果是Android Project,基本代碼不需改變只需將其放入onCreate(...)方法下即可,需要加入asmack.jar包.
1、ConnectionConfiguration
作為用于與XMPP服務(wù)建立連接的配置。它能配置;連接是否使用TLS,SASL加密。
包含內(nèi)嵌類:ConnectionConfiguration.SecurityMode
2、XMPPConnection.
XMPPConnection這個類用來連接XMPP服務(wù).
可以使用connect()方法建立與服務(wù)器的連接。disconnect()方法斷開與服務(wù)器的連接.
在創(chuàng)建連接前可以使用XMPPConnection.DEBUG_ENABLED = true; 使開發(fā)過程中可以彈出一個GUI窗口,用于顯示我們的連接與發(fā)送Packet的信息。

3、ChatManager
用于監(jiān)控當(dāng)前所有chat??梢允褂胏reateChat(String userJID, MessageListener listener)創(chuàng)建一個聊天。
4、Chat
Chat用于監(jiān)控兩個用戶間的一系列message。使用addMessageListener(MessageListener listener)當(dāng)有任何消息到達(dá)時將會觸發(fā)listener的processMessage(Chat chat, Message message)
方法.
我們可以使用sendMessage()發(fā)送消息,這個方法有兩個重載方法,一種類類型的參數(shù)時String類型,另一種則是傳入Message對象(后面介紹)。
那么有這樣一種情況,當(dāng)別人主動跟我們建立連接發(fā)送消息,或者系統(tǒng)發(fā)送消息時我們怎么才能接收消息呢?
我現(xiàn)在是這樣操作的:
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用于表示一個消息包(可以用調(diào)試工具看到發(fā)送包和接收包的具體內(nèi)容)。它有以下多種類型。
- Message.Type.NORMAL -- (默認(rèn))文本消息(比如郵件)
- Message.Type.CHAT -- 典型的短消息,如QQ聊天的一行一行顯示的消息
- Message.Type.GROUP_CHAT -- 群聊消息
- Message.Type.HEADLINE -- 滾動顯示的消息
- Message.TYPE.ERROR -- 錯誤的消息
- Message有兩個內(nèi)部類:
- Message.Body -- 表示消息體
- Message.Type -- 表示消息類型
6、Roster
表示存儲了很多RosterEntry的一個花名冊.為了易于管理,花名冊的項被分貝到了各個group中.
當(dāng)建立與XMPP服務(wù)的連接后可以使用connection.getRoster()獲取Roster對象。
別的用戶可以使用一個訂閱請求(相當(dāng)于QQ加好友)嘗試訂閱目的用戶??梢允褂妹杜e類型Roster.SubscriptionMode的值處理這些請求:
accept_all: 接收所有訂閱請求
reject_all:拒絕所有訂閱請求
manual: 手工處理訂閱請求
創(chuàng)建組:RosterGroup group = roster.createGroup("大學(xué)");
向組中添加RosterEntry對象: group.addEntry(entry);
7、RosterEntry
表示Roster(花名冊)中的每條記錄.它包含了用戶的JID,用戶名,或用戶分配的昵稱.
8、RosterGroup
表示RosterEntry的組。可以使用addEntry(RosterEntry entry)添加。contains(String user) 判斷某用戶是否在組中.當(dāng)然removeEntry(RosterEntry entry)就是從組中移除了。getEntries()
獲取所有RosterEntry.
9、Presence
表示XMPP狀態(tài)的packet。每個presence packet都有一個狀態(tài)。用枚舉類型Presence.Type的值表示:
available -- (默認(rèn))用戶空閑狀態(tài)
unavailable -- 用戶沒空看消息
subscribe -- 請求訂閱別人,即請求加對方為好友
subscribed -- 統(tǒng)一被別人訂閱,也就是確認(rèn)被對方加為好友
unsubscribe -- 他取消訂閱別人,請求刪除某好友
unsubscribed -- 拒絕被別人訂閱,即拒絕對放的添加請求
error -- 當(dāng)前狀態(tài)packet有錯誤
內(nèi)嵌兩個枚舉類型:Presence.Mode和Presence.Type.
可以使用setStatus自定義用戶當(dāng)前的狀態(tài)(像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() {});
//查找服務(wù)
System.out.println(connection.getServiceName());
List<String> col = getConferenceServices(connection.getServiceName(), connection);
for (Object aCol : col) {
String service = (String) aCol;
//查詢服務(wù)器上的聊天室
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());
}
}
}
/*---創(chuàng)建默認(rèn)配置的聊天室 ---
先看看官方的文檔:
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
* 最重要一句:直到用戶調(diào)用join方法的時候聊天室才會被創(chuàng)建
*/
MultiUserChat muc = new MultiUserChat(connection, "instant@conference.pc2010102716");
muc.create("user1");
muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
//----創(chuàng)建手動配置聊天室----
muc = new MultiUserChat(connection, "reserved4@conference.pc2010102716");
//銷毀聊天室
//muc.destroy("Test", null);
muc.create("user2");
//獲取聊天室的配置表單
Form form = muc.getConfigurationForm();
//根據(jù)原始表單創(chuàng)建一個要提交的新表單
Form submitForm = form.createAnswerForm();
//向提交的表單添加默認(rèn)答復(fù)
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());
}
}
//重新設(shè)置聊天室名稱
submitForm.setAnswer("muc#roomconfig_roomname", "Reserved4 Room");
//設(shè)置聊天室的新?lián)碛姓?
List<String> owners = new ArrayList<String>();
owners.add("test@pc2010102716");
submitForm.setAnswer("muc#roomconfig_roomowners", owners);
//設(shè)置密碼
submitForm.setAnswer("muc#roomconfig_passwordprotectedroom", true);
submitForm.setAnswer("muc#roomconfig_roomsecret", "reserved");
//設(shè)置描述
submitForm.setAnswer("muc#roomconfig_roomdesc", "新創(chuàng)建的reserved聊天室");
//設(shè)置聊天室是持久聊天室,即將要被保存下來
//submitForm.setAnswer("muc#roomconfig_persistentroom", true);
//發(fā)送已完成的表單到服務(wù)器配置聊天室
muc.sendConfigurationForm(submitForm);
//加入聊天室(使用昵稱喝醉的毛毛蟲 ,使用密碼ddd)
muc = new MultiUserChat(connection, "ddd@conference.pc2010102716");
muc.join("喝醉的毛毛蟲", "ddd");
//監(jiān)聽消息
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監(jiān)聽器必須在此join方法之前,否則無法監(jiān)聽到需要的5條消息
muc = new MultiUserChat(connection, "ddd@conference.pc2010102716");
DiscussionHistory history = new DiscussionHistory();
history.setMaxStanzas(5);
muc.join("喝醉的毛毛蟲", "ddd", history, SmackConfiguration.getPacketReplyTimeout());
//監(jiān)聽拒絕加入聊天室的用戶
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", "大家來談?wù)勅松?);
//監(jiān)聽邀請加入聊天室請求
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, "你丫很閑啊!");
}
});
//根據(jù)roomJID獲取聊天室信息
RoomInfo roomInfo = MultiUserChat.getRoomInfo(connection, "ddd@conference.pc2010102716");
System.out.println(roomInfo.getRoom() + "-" + roomInfo.getSubject() + "-" + roomInfo.getOccupantsCount());
//判斷用戶是否支持Multi-User聊天協(xié)議
//注:需要加上資源標(biāo)識符
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) (權(quán)限最大的角色,管理其他成員在聊天室中的角色
2:參與者(Participant
3:游客 (Visitor) (不能向所有成員發(fā)送消息)
4:無(沒有角色)(NONE)
*/
/*聊天室用戶可以有5種從屬關(guān)系
* 1、所有者 Owner
* 2、管理員 Admin
* 3、成員 Member
* 4、被驅(qū)逐者 Outcast
* 5、無(不存在從屬關(guān)系) None
*/
//配置聊天室為Moderated聊天室
form = muc.getConfigurationForm();
Form answerForm = form.createAnswerForm();
answerForm.setAnswer("muc#roomconfig_moderatedroom", "1");
muc.sendConfigurationForm(answerForm);
//監(jiān)聽自己的狀態(tài)變更和事件
muc.addUserStatusListener(new DefaultUserStatusListener() {
@Override
public void voiceRevoked() {
super.voiceRevoked();
System.out.println("你被禁言了!");
}
@Override
public void voiceGranted() {
super.voiceGranted();
System.out.println("你被批準(zhǔn)發(fā)言了!");
}
@Override
public void membershipGranted() {
super.membershipGranted();
System.out.println("你被賦予了Member權(quán)限");
}
@Override
public void membershipRevoked() {
super.membershipRevoked();
System.out.println("你被解除了Member權(quán)限");
}
@Override
public void adminGranted() {
super.adminGranted();
System.out.println("你被賦予了管理員權(quán)限");
}
@Override
public void adminRevoked() {
super.adminRevoked();
System.out.println("你被解除了管理員權(quán)限");
}
//......
});
//房主(Owner)批準(zhǔn)test3發(fā)言權(quán)
muc.grantVoice("test3@pc2010102716");
//監(jiān)聽他人狀態(tài)變更
muc.addParticipantStatusListener(new DefaultParticipantStatusListener() {
@Override
public void voiceGranted(String participant) {
super.voiceGranted(participant);
System.out.println(participant + "被批準(zhǔn)發(fā)言了!");
}
@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)批準(zhǔn)test3管理員特權(quán)
muc.grantAdmin("test3@pc2010102716");
//發(fā)送消息
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客戶端的聊天室列表占有者一列不同的原因:當(dāng)使用以下代碼獲取時不能獲取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開發(fā)基于Android的IM系統(tǒng)同理.
- Android Studio和阿里云數(shù)據(jù)庫實現(xiàn)一個遠(yuǎn)程聊天程序
- android Socket實現(xiàn)簡單聊天功能以及文件傳輸
- Android高仿微信聊天界面代碼分享
- android 仿微信聊天氣泡效果實現(xiàn)思路
- Android 應(yīng)用APP加入聊天功能
- Android如何獲取QQ與微信的聊天記錄并保存到數(shù)據(jù)庫詳解
- 詳解Android 獲取手機(jī)中微信聊天記錄方法
- Android藍(lán)牙通信聊天實現(xiàn)發(fā)送和接受功能
- Android實現(xiàn)聊天界面
- Android?Studio實現(xiàn)智能聊天
相關(guān)文章
Android中Splash應(yīng)用啟動白屏問題的解決方法
這篇文章主要為大家詳細(xì)介紹了Android中Splash應(yīng)用啟動白屏問題的兩種解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02
Android實戰(zhàn)教程第十篇仿騰訊手機(jī)助手小火箭發(fā)射效果
這篇文章主要為大家詳細(xì)介紹了Android實戰(zhàn)教程第十篇仿騰訊手機(jī)助手小火箭發(fā)射效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
android camera yuv幀水平翻轉(zhuǎn)實例
今天小編就為大家分享一篇android camera yuv幀水平翻轉(zhuǎn)實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
Android開發(fā)使用Handler的PostDelayed方法實現(xiàn)圖片輪播功能
這篇文章主要介紹了Android開發(fā)使用Handler的PostDelayed方法實現(xiàn)圖片輪播功能,結(jié)合實例形式分析了Android基于Handler的PostDelayed方法實現(xiàn)圖片輪播功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
android應(yīng)用實現(xiàn)開機(jī)自動啟動方法
這篇文章主要介紹了android應(yīng)用實現(xiàn)開機(jī)自動啟動方法,本文講解了原理和編碼實例,需要的朋友可以參考下2015-05-05
解決Android studio 2.3升級到Android studio 3.0 后apt報錯問題
原來項目在Android studio 2.3一切正常,升級到了3.0之后報錯,不支持apt了,其實解決這個問題很簡單,只需要修改兩點(diǎn)內(nèi)容就可以,下面腳本之家小編帶領(lǐng)大家通過本文學(xué)習(xí)吧2017-12-12
RecyclerView實現(xiàn)側(cè)滑拖拽功能
這篇文章主要為大家詳細(xì)介紹了RecyclerView實現(xiàn)側(cè)滑拖拽功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07

