編輯:關於Android編程
簡單的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協議的命名空間:
在網上找了下,有開源的項目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
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, "[email protected]"); muc.create("user1"); muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT)); //----創建手動配置聊天室---- muc = new MultiUserChat(connection, "[email protected]"); //銷毀聊天室 //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, "[email protected]"); 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, "[email protected]"); //muc.join("喝醉的毛毛蟲", "ddd"); //加入聊天室(使用昵稱喝醉的毛毛蟲 ,使用密碼ddd)並且獲取聊天室裡最後5條信息, //注:addMessageListener監聽器必須在此join方法之前,否則無法監聽到需要的5條消息 muc = new MultiUserChat(connection, "[email protected]"); 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, "[email protected]"); 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("[email protected]/飛鳥", 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系統同理.
1、概述隨著Material Design的逐漸的普及,業內也有很多具有分享精神的伙伴翻譯了material design specification,中文翻譯地址:Ma
在開發中往往有要獲取聯系人列表的功能,但是這次卻不是獲取聯系人列表,而是在聯系人列表點擊單個聯系人,獲取單個聯系人的姓名和電話,並設置在指定的輸入框內,方便用戶的使用;以
前沿: 很久沒有寫過博客了,因為工作的原因很少有時間寫東西了,最近想寫一個UI系列的博客,因為我發現這一系列的都很少,而且沒有那麼系統,這裡我想以我自己的觀點來闡述一下如
ChangeMode項目地址:ChangeModeImplementation of night mode for Android.用最簡單的方式實現夜間模式,支持Lis