android asmack 註冊 登陸 聊天 多人聊天室 文件傳輸

XMPP協議簡介

XMPP協議(Extensible Messaging and PresenceProtocol,可擴展消息處理現場協議)是一種基於XML的協議,目的是爲了解決及時通信標準而提出來的,最早是在Jabber上實現的。它繼承了在XML環境中靈活的發展性。因此,基於XMPP的應用具有超強的可擴展性。並且XML很易穿過防火牆,所以用XMPP構建的應用不易受到防火牆的阻礙。利用XMPP作爲通用的傳輸機制,不同組織內的不同應用都可以進行有效的通信。

這篇文章有基本的介紹,http://blog.csdn.net/xutaozero21/article/details/4873439

IM

Instant Messenger,及時通信軟件,就是大家使用的QQ、MSN Messenger和Gtalk等等。其中Gtalk 就是基於XMPP 協議的一個實現,其他的則不是。當前IM 幾乎作爲每個上網者必然使用的工具,在國外的大型企業中有一些企業級的IM應用,但是其商業價值還沒完全發揮出來。設想既然XMPP 協議是一個公開的協議,那麼每個企業都可以利用它來開發適合本身企業工作,提高自身生產效率的IM;甚至,你還可以在網絡遊戲中集成這種通信軟件,不但讓你可以邊遊戲邊聊天,也可以開發出適合遊戲本身的IM 應用,比如說一些遊戲關鍵場景提醒功能,團隊語音交流等等都可以基於IM來實現。

 

本文主要講解在android使用xmpp協議進行即時通信,所涉及3個主要的東西,它們是openfire、smack和spark,這個三個東東結合起來就是完整的xmpp IM實現,這裏簡單介紹一下這3個東東在下文的作用:

openfire主要是作爲服務器,負責管理客戶端的通信連接,以及提供客戶端一些通信信息和連接信息。

Smack主要是xmpp協議的實現,提供了一套很好的api,所以下面操作xmpp都是通過使用smack的api來實現,當然因爲是在android裏,所以使用的是asmack這個包,裏面方法跟smack包差不多。

Spark 是IM客戶端的實現,其實就是使用了smack 的api實現的。

 

下圖展示了三者之間的關係:(很明顯這個圖是偷別人的,具體是哪裏我忘了,因爲資料都是複製到文檔後慢慢研究看的)

image

從圖上可以瞭解到,client 端和server端都可以通過插件的方式來進行擴展,smack是二者傳遞數據的媒介。

 

配置openfire服務器

具體步驟請移步:http://javatech.blog.163.com/blog/static/1766322992010111725339587/

配置成功如果以後ip地址變了,那肯定又是開不了,解決辦法請移步:http://blog.csdn.net/HappySheepherder/article/details/4707124

配置成功後,在服務器創建一個簡單的用戶來測試,然後安裝spark,設置好服務器的ip與端口,使用剛纔創建的用戶登錄,登錄OK說明服務器成功搭建。

Android IM功能(因爲是測試demo,因此界面超級簡陋,代碼都是給出重要的一部分,剩餘的可以在最後下面項目查看)

 

配置要求

android 2.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 ,不過我測試了一下發現不能創建用戶,具體原因不詳,下面介紹第二種。

image

如上圖:註冊成功後服務器將多了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 的聊天

image

image

第二次修改:

第一次的測試,發現如果多個人之間都成爲好友,那麼他們之間的聊天就出現了接收不到的信息,當然在跟spark測試時,spark可以收到android端的信息,不過android客戶端是收到這個信息,不過卻沒有顯示出來,具體原因還不太清楚。因此在第二次修改我改成監聽所有聊天信息包,然後再分析包的歸屬,分發到對應的聊天窗口。

image

 

這裏就是監聽到包後打印的消息,打印出了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源碼,發現有很多可以學習的地方,可以自己定製自己喜歡的類和功能,因此越到後面越發現自己這篇文章很多地方都寫得挺簡單的,差點就不想發了^-^。不過爲了上面那個偉大的理想……

發佈了19 篇原創文章 · 獲贊 8 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章