XMPP協議(Extensible Messaging and PresenceProtocol,可擴展消息處理現場協議)是一種基於XML的協議,目的是爲了解決及時通訊標準而提出來的,最先是在Jabber上實現的。它繼承了在XML環境中靈活的發展性。所以,基於XMPP的應用具備超強的可擴展性。而且XML很易穿過防火牆,因此用XMPP構建的應用不易受到防火牆的阻礙。利用XMPP做爲通用的傳輸機制,不一樣組織內的不一樣應用均可以進行有效的通訊。 html
這篇文章有基本的介紹,http://blog.csdn.net/xutaozero21/article/details/4873439java
Instant Messenger,及時通訊軟件,就是你們使用的QQ、MSN Messenger和Gtalk等等。其中Gtalk 就是基於XMPP 協議的一個實現,其餘的則不是。當前IM 幾乎做爲每一個上網者必然使用的工具,在國外的大型企業中有一些企業級的IM應用,可是其商業價值還沒徹底發揮出來。設想既然XMPP 協議是一個公開的協議,那麼每一個企業均可以利用它來開發適合自己企業工做,提升自身生產效率的IM;甚至,你還能夠在網絡遊戲中集成這種通訊軟件,不但讓你能夠邊遊戲邊聊天,也能夠開發出適合遊戲自己的IM 應用,好比說一些遊戲關鍵場景提醒功能,團隊語音交流等等均可以基於IM來實現。 android
本文主要講解在android使用xmpp協議進行即時通訊,所涉及3個主要的東西,它們是openfire、smack和spark,這個三個東東結合起來就是完整的xmpp IM實現,這裏簡單介紹一下這3個東東在下文的做用: 程序員
openfire主要是做爲服務器,負責管理客戶端的通訊鏈接,以及提供客戶端一些通訊信息和鏈接信息。 api
Smack主要是xmpp協議的實現,提供了一套很好的api,因此下面操做xmpp都是經過使用smack的api來實現,固然由於是在android裏,因此使用的是asmack這個包,裏面方法跟smack包差很少。 數組
Spark 是IM客戶端的實現,其實就是使用了smack 的api實現的。 服務器
下圖展現了三者之間的關係:(很明顯這個圖是偷別人的,具體是哪裏我忘了,由於資料都是複製到文檔後慢慢研究看的) 網絡
從圖上能夠了解到,client 端和server端均可以經過插件的方式來進行擴展,smack是兩者傳遞數據的媒介。 session
具體步驟請移步:http://javatech.blog.163.com/blog/static/1766322992010111725339587/app
配置成功若是之後ip地址變了,那確定又是開不了,解決辦法請移步:http://blog.csdn.net/HappySheepherder/article/details/4707124
配置成功後,在服務器建立一個簡單的用戶來測試,而後安裝spark,設置好服務器的ip與端口,使用剛纔建立的用戶登陸,登陸OK說明服務器成功搭建。
Android IM功能(由於是測試demo,所以界面超級簡陋,代碼都是給出重要的一部分,剩餘的能夠在最後下面項目查看)
android 2.二、 asmack-jse.jar、myeclipse
在打開軟件後會開始初始化,完成與openfire服務器的鏈接,設置一些配置
static { XMPPConnection.DEBUG_ENABLED = true; final ConnectionConfiguration connectionConfig = new ConnectionConfiguration( host, 5222, ""); // Google talk // ConnectionConfiguration connectionConfig = new // ConnectionConfiguration( // "talk.google.com", 5222, "gmail.com");
// connectionConfig.setSASLAuthenticationEnabled(false); ActivityMain.connection = new XMPPConnection(connectionConfig); ActivityMain.connection.DEBUG_ENABLED = true; ProviderManager pm = ProviderManager.getInstance(); configure(pm); }
註冊有兩種方法:一種是用createAccount ,不過我測試了一下發現不能建立用戶,具體緣由不詳,下面介紹第二種。
如上圖:註冊成功後服務器將多了ggg用戶。
具體實現以下:
Registration reg = new Registration(); reg.setType(IQ.Type.SET); reg.setTo(ConnectionSingleton.getInstance().getServiceName()); reg.setUsername(username.getText().toString()); reg.setPassword(password.getText().toString()); reg.addAttribute("android", "geolo_createUser_android"); System.out.println("reg:" + reg); PacketFilter filter = new AndFilter(new PacketIDFilter(reg .getPacketID()), new PacketTypeFilter(IQ.class)); PacketCollector collector = ConnectionSingleton.getInstance() .createPacketCollector(filter); ConnectionSingleton.getInstance().sendPacket(reg); result = (IQ) collector.nextResult(SmackConfiguration .getPacketReplyTimeout()); // Stop queuing results collector.cancel(); if (result == null) { Toast.makeText(getApplicationContext(), "服, Toast.LENGTH_SHORT).show(); } else if (result.getType() == IQ.Type.ERROR) { if (result.getError().toString().equalsIgnoreCase( "conflict(409)")) { Toast.makeText(getApplicationContext(), "這, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getApplicationContext(), "注, Toast.LENGTH_SHORT).show(); } } else if (result.getType() == IQ.Type.RESULT) { Toast.makeText(getApplicationContext(), "恭, Toast.LENGTH_SHORT).show(); }
使用註冊類,設置好註冊的用戶名密碼和一些屬性字段,直接設置包過濾,根據這個過濾建立一個結果集合,發送註冊信息包,等待獲取結果,剩餘就是判斷結果內容.
登陸比較簡單
ConnectionSingleton.getInstance().connect();// connect String account = etUsername.getText().toString(); String password = etPassword.getText().toString(); // 保存用戶和密碼
ActivityLogin.util.saveString(ACCOUNT_KEY, account); ActivityLogin.util.saveString(PASSWORD_KEY, password); ConnectionSingleton.getInstance().login(account, password);// login // login success System.out.println("login success"); ActivityLogin.mCurrentAccount = account; System.out.println(ConnectionSingleton.getInstance() .getUser()); // 登陸成功後發如今線狀態
Presence presence = new Presence(Presence.Type.available); ConnectionSingleton.getInstance().sendPacket(presence); // 開始主界面
Intent intent = new Intent(ActivityLogin.this, ActivityMain.class); startActivity(intent);
獲取聯繫人模塊(ActivityMain 主界面)
獲取聯繫人並將相關信息保存到一個list數組裏,最後通知listview更新界面
roster = ActivityMain.connection.getRoster();
public void updateRoster() { 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()); User user = new User(); user.setName(entry.getName()); user.setUser(entry.getUser()); user.setType(entry.getType()); user.setSize(entry.getGroups().size()); user.setStatus(presence.getStatus()); user.setFrom(presence.getFrom()); userinfos.add(user); } rosterAdapter.notifyDataSetChanged(); }
第一次修改:
在主界面點擊選擇一個用戶,進入聊天Activity,ActivityChat先獲取傳過來的用戶,建立聊天類並對該用戶設置消息監聽
ChatManager chatmanager = ConnectionSingleton.getInstance() .getChatManager(); // get user Intent intent = getIntent(); String user = intent.getStringExtra("user"); System.out.println("user:" + user); // new a session newChat = chatmanager.createChat(user, null); // 監聽聊天消息
chatmanager.addChatListener(new ChatManagerListenerEx()); // send message try { newChat.sendMessage("im bird man"); } catch (XMPPException e) { // TODO Auto-generated catch block e.printStackTrace(); }
監聽類
public class ChatManagerListenerEx implements ChatManagerListener { @Override public void chatCreated(Chat chat, boolean arg1) { // TODO Auto-generated method stub chat.addMessageListener(ml); } } public class MessageListenerEx implements MessageListener { @Override public void processMessage(Chat arg0, Message message) { String result = message.getFrom() + ":" + message.getBody(); System.out.println(result); android.os.Message msg = new android.os.Message(); msg.what = 0; Bundle bd = new Bundle(); bd.putString("msg", result); msg.setData(bd); handler.sendMessage(msg); } }
所獲取到的消息都是經過handler來更新UI
public Handler handler = new Handler() { @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { case 0: { String result = msg.getData().getString("msg"); record.setText(record.getText() + "\n" + result); } break; default: break; } } };
aaa跟bbb 的聊天
第二次修改:
第一次的測試,發現若是多我的之間都成爲好友,那麼他們之間的聊天就出現了接收不到的信息,固然在跟spark測試時,spark能夠收到android端的信息,不過android客戶端是收到這個信息,不過卻沒有顯示出來,具體緣由還不太清楚。所以在第二次修改我改爲監聽全部聊天信息包,而後再分析包的歸屬,分發到對應的聊天窗口。
這裏就是監聽到包後打印的消息,打印出了jid和消息內容
public class XmppMessageManager implements ChatManagerListener { private XMPPConnection _connection; private ChatManager manager = null; public void initialize(XMPPConnection connection) { _connection = connection; manager = _connection.getChatManager(); manager.addChatListener(this); } @Override public void chatCreated(Chat chat, boolean arg1) { // TODO Auto-generated method stub chat.addMessageListener(new MessageListener() { public void processMessage(Chat newchat, Message message) { // 如果聊天窗口存在,將消息轉往目前窗口 // 若窗口不存在,建立新的窗口 System.out .println(message.getFrom() + ":" + message.getBody()); if (!ActivityMain.chats.containsKey(message.getFrom())) { ActivityMain.chats.put(message.getFrom(), newchat); } else { } } }); } }
主要就是重寫了ChatManagerListener類的監聽,分發處理暫時沒有想好怎麼寫。接着在後臺啓動service就能夠開始監聽,行了第一次修改那些能夠去掉了^0^。
也是在主界面的菜單進入ActivityMultiChat,該界面顯示所建立的房間,點擊就跳轉到ActivityMultiRoom 。
獲取全部房間比較簡單,只需執行下面這段代碼
hostrooms = MultiUserChat.getHostedRooms(ActivityMain.connection,
"conference.zhanghaitao-pc");
跳轉到後獲取要加入的房間的jid,並建立監聽。
jid = getIntent().getStringExtra("jid");
//後面服務名稱必需是建立房間的那個服務
String multiUserRoom = jid; try { muc = new MultiUserChat(ActivityMain.connection, multiUserRoom); //建立聊天室,進入房間後的nickname
muc.join(ActivityLogin.mCurrentAccount); Log.v(TAG, "join success"); } catch (XMPPException e) { // TODO Auto-generated catch block e.printStackTrace(); } ChatPacketListener chatListener = new ChatPacketListener(muc); muc.addMessageListener(chatListener);
監聽大概的流程跟單人聊天差很少,都是handler來操做。不過多人聊天是重寫了PacketListener。具體以下(不過該方法是監聽房間的信息,也就是說顯示的是以房間爲名字的消息):
class ChatPacketListener implements PacketListener { private String _number; private Date _lastDate; private MultiUserChat _muc; private String _roomName; public ChatPacketListener(MultiUserChat muc) { _number = "0"; _lastDate = new Date(0); _muc = muc; _roomName = muc.getRoom(); } @Override public void processPacket(Packet packet) { Message message = (Message) packet; String from = message.getFrom(); if (message.getBody() != null) { DelayInformation inf = (DelayInformation) message.getExtension( "x", "jabber:x:delay"); Date sentDate; if (inf != null) { sentDate = inf.getStamp(); } else { sentDate = new Date(); } Log.w(TAG, "Receive old message: date=" + sentDate.toLocaleString() + " ; message=" + message.getBody()); android.os.Message msg = new android.os.Message(); msg.what = RECEIVE; Bundle bd = new Bundle(); bd.putString("from", from); bd.putString("body", message.getBody()); msg.setData(bd); handler.sendMessage(msg); } } }
在主界面對着用戶名長按,進入下載activity。進入activityFileTransfer,點擊傳輸按鈕便可將文件傳輸給以前選擇的用戶,固然這裏作得比較簡單,並無拒絕功能,一旦發現有文件就接受。
FileTransferManager transfer = new FileTransferManager( ActivityMain.connection); String destination = user; OutgoingFileTransfer out = transfer .createOutgoingFileTransfer(destination + "/Smack");
那用戶是如何監聽到有文件而且接受呢?在進入主界面的時候就已經開始了一個service(fileListenerService),該服務建立文件的監聽類(XmppFileManager),監聽類主要繼承FileTransferListener 重寫裏面的fileTransferRequest方法。
File saveTo;
// set answerTo for replies and send()
answerTo = request.getRequestor(); if (!Environment.MEDIA_MOUNTED.equals(Environment .getExternalStorageState())) { send("External Media not mounted read/write"); return; } else if (!landingDir.isDirectory()) { send("The directory " + landingDir.getAbsolutePath() + " is not a directory"); return; } saveTo = new File(landingDir, request.getFileName()); if (saveTo.exists()) { send("The file " + saveTo.getAbsolutePath() + " already exists"); // delete saveTo.delete(); // return; } IncomingFileTransfer transfer = request.accept(); send("File transfer: " + saveTo.getName() + " - " + request.getFileSize() / 1024 + " KB"); try { transfer.recieveFile(saveTo); send("File transfer: " + saveTo.getName() + " - " + transfer.getStatus()); double percents = 0.0; while (!transfer.isDone()) { if (transfer.getStatus().equals(Status.in_progress)) { percents = ((int) (transfer.getProgress() * 10000)) / 100.0; send("File transfer: " + saveTo.getName() + " - " + percents + "%"); } else if (transfer.getStatus().equals(Status.error)) { send(returnAndLogError(transfer)); return; } Thread.sleep(1000); } if (transfer.getStatus().equals(Status.complete)) { send("File transfer complete. File saved as " + saveTo.getAbsolutePath()); } else { send(returnAndLogError(transfer)); } } catch (Exception ex) { String message = "Cannot receive the file because an error occured during the process." + ex; Log.e(TAG, message, ex); send(message); }
網上說文件的傳輸遇到幾個比較重要的問題,我也查看了不少資料(國內的基本是寥寥無幾,對此我感到挺無奈的,只能看國外,這樣每次個人英語水平都提升了太無奈了^0^)。在這個android asmack的最新版本好像是解決了幾個比較重要的問題,剩下一個傳輸文件沒反應的問題我在初始化鏈接時調用了configure方法解決。不過不知道是否是這個緣由,後面出現了一個神奇的問題,就是有時能夠成功傳輸有時傳輸時客戶端沒響應(若是有人完美解決了這個問題,那就………趕忙將代碼放出來一塊兒共享^-^,以提升我國程序員的總體水平,多偉大遙遠的理想啊~~)。
項目下載
http://files.cnblogs.com/not-code/ASmack.zip
本文爲原創翻譯,如需轉載,請註明做者和出處,謝謝!
出處:http://www.cnblogs.com/not-code/archive/2011/07/16/2108369.html
文章其實在一個星期前就寫好了, 不過一直忙着看asmack源碼,發現有不少能夠學習的地方,能夠本身定製本身喜歡的類和功能,所以越到後面愈加現本身這篇文章不少地方都寫得挺簡單的,差點就不想發了^-^。不過爲了上面那個偉大的理想……