Android平臺下基於XMPP的IM研究(二 MultiUserChat 聊天室)
一直想寫東西,但是上班不能在CSDN上寫東西。下班回家又忙著學Iphone開發,唉,兩個個字,忒忙。今天就寫寫Smack的聊天室功能吧。
先上程式碼:
- 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系統同理.
相關文章
- Android學習筆記--基於XMPP的即時通訊Android筆記
- 基於XMPP協議開發Android即時通訊軟體協議Android
- 基於webapi的websocket聊天室(番外二)WebAPI
- Web快速開發平臺,基於二次開發平臺Web
- 基於Android平臺實現人臉識別Android
- 基於Netty的Android系統IM簡單實現原理NettyAndroid
- iOS實現XMPP通訊(二)XMPP程式設計iOS程式設計
- 聊天室應用開發實踐(二):實現基於 Web 的聊天室Web
- 基於 Springboot+vue 的介面自動化平臺 (二)Spring BootVue
- 基於netty的聊天室Netty
- 基於golang的聊天室Golang
- 微服務平臺下基於 GraphQL 構建 BFF 的思考微服務
- 基於Netty,從零開發IM(二):編碼實踐篇(im單聊功能)Netty
- ---慢更-基於 go 的 IM 聊天Go
- 一種基於Android、iOS平臺的移動端VIN碼識別SDK,支援二次開發AndroidiOS
- 基於webapi的websocket聊天室(四)WebAPI
- QuickMock:基於Express的快速mock平臺UIMockExpress
- 基於kubernetes平臺微服務的部署微服務
- 基於java的專案管理平臺Java專案管理
- 開源一個自用的Android IM庫,基於Netty+TCP+Protobuf實現。AndroidNettyTCP
- 基於Gin的IM聊天架構——HiChat架構
- WinForm的Socket實現簡單的聊天室 IMORM
- 使用CMake命令編譯Android平臺下的包編譯Android
- android展訊平臺 重啟案例分析(二)Android
- 基於JTT809協議的車輛資訊交換平臺架構方案(下級平臺)協議架構
- BizWorks應⽤平臺基於KubeVela的實踐
- 基於Saltstack、Artifactory打造傳統模式下持續部署平臺模式
- 基於Netty,徒手擼IM(一):IM系統設計篇Netty
- 基於webapi的websocket聊天室(番外一)WebAPI
- C++ 實現基於TCP的聊天室C++TCP
- 基於 golang + vue + websocket 開發的聊天室GolangVueWeb
- 基於 Serverless 的部署平臺構建與思考Server
- 基於 Django 的 Dubbo 介面測試工具平臺Django
- 攜程基於Flink的實時特徵平臺特徵
- 基於 Kubernetes 的雲原生 AI 平臺建設AI
- BizWorks 應用平臺基於 KubeVela 的實踐
- 基於chaosblade的故障注入平臺小試
- 融雲 IM 在 Electron 平臺上的設計實踐
- 基於IM場景下的Wasm初探:提升Web應用效能|得物技術ASMWeb