最近在學習XMPP的使用,打算完成一個完整較爲完整地Demo示例,通過這個示例掌握xmpp的使用與開發。同時打算在這個示例中學習使用一下其他的開源類庫,在此作爲記錄學習。
包括服務器端——Openfire,客戶端——Spark,XMPP 傳輸協議的實現——Smack(XMPP是一個協議,協議是需要實現的,Smack起到的就是這樣的一個作用,android開發使用的是asmack類庫)。三者都是基於Java 語言的實現,因此對於熟悉Java 的開發者來說不是很難。
OpenFire介紹
Openfire 採用Java開發,開源的實時協作(RTC)服務器基於XMPP(Jabber)協議。
您可以使用它輕易的構建高效率的即時通信服務器.
Openfire安裝和使用都非常簡單,並利用Web進行管理。單臺服務器可支持上萬併發用戶。
由於是採用開放的XMPP協議,您可以使用各種支持XMPP協議的IM客戶端軟件登陸服務.
Spark介紹
Spark 提供了客戶端一個基本的實現,並提出了一個很好的插件架構,這對於開發者來說不能不說是一個福音。我強烈建議基於插件方式來實現你新增加的功能,而不是去改它的源代碼,這樣有利於你項目架構,把原始項目的影響降到最低,文章以後的部分也是基於這種插件體系進行開發的
Asmack介紹
smack的Android版本,雖然Smack在PC上可以工作的很好,功能也很強大,但在Android平臺上有一些問題,而導致這些問題的原因是Android精簡了Java的類庫,以至Smack使用的部分類庫在Android平臺上無法找到,所以Smack不能直接在Android平臺上使用.但在2010年初,有人在code.google.com網站上發佈了一個Asmack,其中A庫就代表Android中的A,也就是說,這個版本是Smack的Android版本.可使用下面地址下載Asmack:
http://code.google.com/p/asmack/downloads/list
下載asmack-2010.03.03.jar文件後,直接在Android工程中引用即可
關係圖
登陸操作界面:
示意說明:
1、輸入用戶名、密碼、服務器地址,可以在搭建好openfire服務器後用spark註冊賬號,類似qq,多註冊幾個作爲測試用,我用spark建立賬號名和密碼都爲test,在此賬號創建2個組,我的好友和大學同學,同時註冊test1-test4的測試賬號,加test爲好友,當然這些都可以用代碼操作,初始階段我是這麼做快速寫代碼測試。
2、輸入錯誤的賬號和密碼會看到信息展示在登陸失敗後將錯誤信息展示出來
3、當輸入正確用戶名和密碼之後,進入mainActivity界面,這裏將分組和所有好友以文字形式展現出來
代碼:
XMPPManager.java
package manager; import org.jivesoftware.smack.*; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smackx.GroupChatInvitation; import org.jivesoftware.smackx.PrivateDataManager; import org.jivesoftware.smackx.packet.*; import org.jivesoftware.smackx.provider.*; import org.jivesoftware.smackx.search.UserSearch; import java.util.Collection; import java.util.Iterator; /** * User: Coolwxb * Date: 14-1-13 * Time: 下午5:10 */ public class XMPPManager { private static XMPPManager instance = null; XMPPConnection connection=null; private static String URL = "10.0.0.14"; private static int PORT = 5222; private static String DEVICE = "pc"; private static String USERNAME = "test"; private static String PASSWORD = "test"; private static int SUCCESS=0; private XMPPManager( ){ /** Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of providers exist: IQProvider -- parses IQ requests into Java objects. PacketExtension -- parses XML sub-documents attached to packets into PacketExtension instances. **/ ProviderManager pm = ProviderManager.getInstance(); configure(pm); } /** * 單例方法 * @return */ public static XMPPManager getInstance() { if (instance == null) { instance = new XMPPManager(); } return instance; } /** * 獲得connection連接 * @return */ public XMPPConnection getConnection() throws Exception { if (connection == null) throw new Exception("請先初始化xmppconnection"); return connection; } /** * * 登陸操作 返回String 來判斷登陸結果 Code XMPP Error Type 500 interna-server-error WAIT 403 forbidden AUTH 400bad-request MODIFY 404 item-not-found CANCEL 409 conflict CANCEL 501 feature-not-implemented CANCEL 302 gone MODIFY 400 jid-malformed MODIFY 406 no-acceptable MODIFY 405 not-allowed CANCEL 401 not-authorized AUTH 402 payment-required AUTH 404 recipient-unavailable WAIT 302 redirect MODIFY 407 registration-required AUTH 404 remote-server-not-found CANCEL 504 remote-server-timeout WAIT 502 remote-server-error CANCEL 500 resource-constraint WAIT 503 service-unavailable CANCEL 407 subscription-required AUTH 500 undefined-condition WAIT 400 unexpected-condition WAIT 408 request-timeout CANCEL * * @param username * @param password * @param server*/ public String isLogin(String username, String password, String server) { try { ConnectionConfiguration cf = new ConnectionConfiguration( server, PORT, DEVICE); cf.setDebuggerEnabled(true); //開啓debug模式 cf.setCompressionEnabled(false); //是否對流進行壓縮 cf.setSASLAuthenticationEnabled(false); //是否開啓SASL 登陸驗證 connection = new XMPPConnection(cf); connection.connect(); connection.login(username, password); return SUCCESS+""; } catch (XMPPException e) { return e.getMessage(); } } public void configure(ProviderManager pm) { // Private Data Storage pm.addIQProvider("query", "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); // Time try { pm.addIQProvider("query", "jabber:iq:time", Class.forName("org.jivesoftware.smackx.packet.Time")); } catch (ClassNotFoundException e) { } // XHTML pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); // Roster Exchange pm.addExtensionProvider("x", "jabber:x:roster", new RosterExchangeProvider()); // Message Events pm.addExtensionProvider("x", "jabber:x:event", new MessageEventProvider()); // Chat State pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); // FileTransfer pm.addIQProvider("si", "http://jabber.org/protocol/si", new StreamInitiationProvider()); // Group Chat Invitations pm.addExtensionProvider("x", "jabber:x:conference", new GroupChatInvitation.Provider()); // 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()); // Data Forms pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider()); // MUC User pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider()); // MUC Admin pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); // MUC Owner pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); // Delayed Delivery pm.addExtensionProvider("x", "jabber:x:delay", new DelayInformationProvider()); // Version try { pm.addIQProvider("query", "jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version")); } catch (ClassNotFoundException e) { } // VCard pm.addIQProvider("vCard", "vcard-temp", new VCardProvider()); // 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()); // Last Activity pm.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider()); // User Search pm.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider()); // SharedGroupsInfo pm.addIQProvider("sharedgroup", "http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider()); // JEP-33: Extended Stanza Addressing pm.addExtensionProvider("addresses", "http://jabber.org/protocol/address", new MultipleAddressesProvider()); } /** * 返回組信息的容器 * @param roster * @return */ public Iterator<RosterGroup> getGroups(Roster roster) { Collection<RosterGroup> rosterGroups = roster.getGroups(); return rosterGroups.iterator(); } }
這裏寫了個XMPPManager作爲幫助類,主要重要的方法就是對xmppconnection做初始化,可看到configure方法對用xml描述的xmpp包做解析,官方文檔是這樣對ProviderManager這個類做解釋:
Manages providers for parsing custom XML sub-documents of XMPP packets.
Two types of providers exist: IQProvider -- parses IQ requests into Java objects. PacketExtension -- parses XML sub-documents attached to packets
into PacketExtension instances.
LoginActivity.java
package com.example.XMPPDemo; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import manager.XMPPManager; import org.jivesoftware.smack.XMPPConnection; /** * User: Coolwxb * Date: 14-1-13 * Time: 下午5:18 */ public class LoginActivity extends Activity{ private XMPPManager xmppManager; private XMPPConnection xmppConnection; private Button btn_login; private EditText et_username; private EditText et_password; private EditText et_server; private TextView tv_info; ProgressDialog pd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login); et_password = (EditText) findViewById(R.id.et_password); et_username = (EditText) findViewById(R.id.et_username); et_server = (EditText) findViewById(R.id.et_server); tv_info = (TextView) findViewById(R.id.tv_info); btn_login = (Button) findViewById(R.id.btn_login); btn_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { login(); } }); } private void login() { new AsyncTask< String, String, String > (){ String username = ""; String password = ""; String server = ""; @Override protected void onPreExecute() { username = et_username.getText().toString().trim(); password = et_password.getText().toString().trim(); server = et_server.getText().toString().trim(); xmppManager = XMPPManager.getInstance(); pd = new ProgressDialog(LoginActivity.this); pd.setTitle("提示"); pd.setMessage("正在登陸。。。。"); pd.show(); } @Override protected String doInBackground(String... strings) { return xmppManager.isLogin(username,password,server); } @Override protected void onPostExecute(String info) { pd.dismiss(); if ("0".equals(info)){ //成功登陸SUCCESS Intent intent = new Intent(); intent.setClass(LoginActivity.this, MainActivity.class); startActivity(intent); }else { Log.e("error",info); tv_info.setText(info); } } }.execute(null,null,null); } }
這裏使用了一個android 的異步線程類AsyncTask,在doInBackground方法中做一些耗時的操作,我試過如果不加入線程中操作的話會出現假死現象,所以在這裏我將登陸連接放入了線程中.
MainActivity.java
package com.example.XMPPDemo; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import manager.XMPPManager; import org.jivesoftware.smack.Roster; import org.jivesoftware.smack.RosterEntry; import org.jivesoftware.smack.RosterGroup; import java.util.Collection; import java.util.Iterator; /** * User: Coolwxb * Date: 14-1-13 * Time: 下午6:28 */ public class MainActivity extends Activity{ XMPPManager xmppManager; private TextView tv_groupInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_groupInfo = (TextView) findViewById(R.id.tv_groupinfo); xmppManager = XMPPManager.getInstance(); Roster roster = null; try { roster = xmppManager.getConnection().getRoster(); } catch (Exception e) { e.printStackTrace(); } Iterator<RosterGroup> rosterGroupIterator = xmppManager.getGroups(roster); StringBuilder sb = new StringBuilder(); //獲取所有組信息 if (rosterGroupIterator.hasNext()){ //如果有分組 while (rosterGroupIterator.hasNext()) { RosterGroup rosterGroup = rosterGroupIterator.next(); String groupName = rosterGroup.getName(); int count = rosterGroup.getEntryCount(); sb.append(rosterGroup.getName()+"\r\n"); } tv_groupInfo.setText("組信息:+\r\n"+sb.toString()); } else{ tv_groupInfo.setText("沒有組信息"); } //獲取所有人 Collection<RosterEntry> iterator =roster.getEntries(); Iterator<RosterEntry> entryIterator = iterator.iterator(); sb = new StringBuilder(); while (entryIterator.hasNext()) { RosterEntry rosterEntry = entryIterator.next(); sb.append(rosterEntry.getName()+"\r\n"); } String ii = sb.toString(); Log.i("info", ii); tv_groupInfo.append("所有的成員:+\r\n"+ii); } }
MainActivity的界面中我只放入了一個textview,用來顯示分組信息和成員。因爲在操作開始我用spark向test這個賬號建立了分組和好友,所以會獲取到。
登陸就介紹到這裏,跳轉到mainactivity獲取組信息和成員信息涉及比較多的常用類,打算專門研究下專門寫一章。
最後附上一些比較重要的東東,希望大家會喜歡,同時也希望大家提出寶貴意見,大家共同進步。
登陸源碼:
xmpp幫助文檔:
asmack jar包
最後吐槽下windows live writer 今天用的好卡。。。卡死3次。。什麼心情。。