基于 Smack 的 xmpp 學(xué)習(xí)筆記

XMPP 開發(fā)學(xué)習(xí)

由于 aSmack 已經(jīng)棄用,目前使用的是 smack 原版 4.2.0

aSmack is deprecated and obsolete. Starting with Version 4.1 Smack is able to run without modifications on Android.

More information on how to use Smack 4.1 in your Android Project can be found in the Smack 4.1 Readme and Upgrade Guide.

smack 的 github repo

Instructions how to use Smack in your Java or Android project are provided in the Smack 4.2 Readme and Upgrade Guide.

使用Android 的同學(xué)可以進(jìn)入上面的鏈接危号。

Android 需要依賴的

dependencies {
  compile "org.igniterealtime.smack:smack-android-extensions:4.2.0"
  compile "org.igniterealtime.smack:smack-tcp:4.2.0"
}

A typical Smack setup may also want to additional declare dependencies on smack-tcp, smack-extensions and smack-experimental

如果是一個(gè)完整的 xmpp 還需要額外依賴 這幾個(gè)庫(kù)橡疼,我沒有使用

但是后面在做注冊(cè)用戶這個(gè)需求的時(shí)候發(fā)現(xiàn) AccountManager這個(gè)類在 smack-extensions里面,如果需要一些完整的功能揭朝,還是全加上好了队贱,或者按照自己需求。

完整的 JID
Username@Domain/Resource
基本組成部分

Node/Username - 用戶名/節(jié)點(diǎn) 用戶的基本標(biāo)識(shí)
Domain - 登陸的XMPP服務(wù)器域名
Resource - 資源/來源潭袱,用于區(qū)別客戶端來源, XMPP協(xié)議設(shè)計(jì)為可多客戶端同時(shí)登陸, Resource就是用于區(qū)分同一用戶不同端登陸
Bare - 除去Resource部分, 包含Username@Domain

接口使用

連接 (Connection)

public void connect() {

        try {
            XMPPTCPConnectionConfiguration.Builder configBuilder = XMPPTCPConnectionConfiguration.builder();
            configBuilder.setHostAddress(InetAddress.getByName(SERVER_IP));
            configBuilder.setHost(SERVER_IP);
            configBuilder.setPort(SERVER_PORT);
            configBuilder.setXmppDomain(SERVER_IP);
            configBuilder.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);
            configBuilder.setDebuggerEnabled(true);
            configBuilder.setCompressionEnabled(true);
            configBuilder.setSendPresence(false);

            mConnection = new XMPPTCPConnection(configBuilder.build());

            mConnection.connect();

            Log.d(TAG, "connection status is -> " + String.valueOf(mConnection.isConnected()));
        } catch (UnknownHostException e) {
            e.printStackTrace();
            Log.d(TAG, e.toString());
        } catch (XmppStringprepException e) {
            e.printStackTrace();
            Log.d(TAG, e.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
            Log.d(TAG, e.toString());
        } catch (IOException e) {
            e.printStackTrace();
            Log.d(TAG, e.toString());
        } catch (SmackException e) {
            e.printStackTrace();
            Log.d(TAG, e.toString());
        } catch (XMPPException e) {
            e.printStackTrace();
            Log.d(TAG, e.toString());
        }
    }

端口記得要去openfire后臺(tái)去看柱嫌,默認(rèn)是 5222, 我一開始寫成了9090,后來發(fā)現(xiàn)9090 的openfire 管理后臺(tái)的端口屯换。切記切記

登錄(Login)

/**
     * 登錄
     * @param user_name 用戶名
     * @param passwd 密碼
     * @return 是否成功
     */
    fun login(user_name: String, passwd: String): Boolean {
        log_d(TAG, "login")
        if (mConnection != null && mConnection!!.isConnected) {
            try {
                mConnection?.login(user_name, passwd)
                setStatus(XMPP_STATUS_ONLINE)

//                mConnection?.addConnectionListener(this)
                ChatManager.getInstanceFor(mConnection).addIncomingListener(this)
                ChatManager.getInstanceFor(mConnection).addOutgoingListener(this)

                log_d(TAG, "login successful")
                return true

            } catch (e: XMPPException) {
                e.printStackTrace()
                log_e(TAG, e.toString())
            } catch (e: SmackException) {
                e.printStackTrace()
                log_e(TAG, e.toString())
                if (e is SmackException.AlreadyLoggedInException) {
                    return true
                }
            } catch (e: IOException) {
                e.printStackTrace()
                log_e(TAG, e.toString())
            } catch (e: InterruptedException) {
                e.printStackTrace()
                log_e(TAG, e.toString())
            }

        }
        return false
    }

setStatus是修改狀態(tài)

/**
     * 更改用戶狀態(tài)
     * @param code 狀態(tài)常量
     */
    fun setStatus(code: Int) {
        log_d(TAG, "setStatus")
        if (mConnection != null && mConnection!!.isConnected) {

            try {
                var presence: Presence? = null

                when (code) {
                    XMPP_STATUS_ONLINE -> {
                        log_d(TAG, "設(shè)置在線")
                        presence = Presence(Presence.Type.available)
                    }

                    XMPP_STATUS_CHAT_ME -> {
                        log_d(TAG, "設(shè)置Q我吧")
                        presence = Presence(Presence.Type.available)
                        presence.mode = Presence.Mode.chat
                    }

                    XMPP_STATUS_BUSY -> {
                        log_d(TAG, "設(shè)置忙碌")
                        presence = Presence(Presence.Type.available)
                        presence.mode = Presence.Mode.dnd
                    }

                    XMPP_STATUS_LEAVE -> {
                        log_d(TAG, "設(shè)置離開")
                        presence = Presence(Presence.Type.available)
                        presence.mode = Presence.Mode.away
                    }

                    XMPP_STATUS_OFFLINE -> {

                        log_d(TAG, "設(shè)置離線")
                        presence = Presence(Presence.Type.unavailable)
                    }
                }

                mConnection?.sendStanza(presence)
                log_d(TAG, "set status successful")
            } catch (e: SmackException.NotConnectedException) {
                e.printStackTrace()
                log_e(TAG, e.toString())
                connect()
            } catch (e: InterruptedException) {
                e.printStackTrace()
                log_e(TAG, e.toString())
            }


        }
    }

注冊(cè)(Register)

java.lang.IllegalStateException: Creating account over insecure connection

不安全的連接創(chuàng)建 account

accountManager.sensitiveOperationOverInsecureConnection(true);

現(xiàn)在不安全了

public boolean registerUser(String user_name, String passwd) {
        if (mConnection != null && mConnection.isConnected()) {
            // 已經(jīng)connect 上了编丘,才可以進(jìn)行注冊(cè)操作
            try {
                AccountManager accountManager = AccountManager.getInstance(mConnection);
                if (accountManager.supportsAccountCreation()) {
                    accountManager.sensitiveOperationOverInsecureConnection(true);
                    accountManager.createAccount(Localpart.from(user_name), passwd);

                    return true;
                }
            } catch (SmackException.NoResponseException e) {
                e.printStackTrace();
            } catch (XMPPException.XMPPErrorException e) {
                e.printStackTrace();
                if (e.getXMPPError().getCondition() == XMPPError.Condition.conflict) {
                    // 用戶名已存在
                }
            } catch (SmackException.NotConnectedException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (XmppStringprepException e) {
                e.printStackTrace();
            }
        }

        return false;

    }

創(chuàng)建成功。openfile 可以看到彤悔,雖然沒有 name [doge]

如果已經(jīng)存在用戶的嘉抓,則會(huì)有異常。

發(fā)送消息(Send Message)

/**
     * 發(fā)送單人聊天 消息
     * @param chat 單人聊天室
     * @param message 發(fā)送的消息
     */
    fun sendSingleMessage(chat: Chat, message: String) {
        log_d(TAG, "sendSingleMessage->$message")
        if (mConnection != null) {
            try {
                chat.send(message)
            } catch (e: SmackException.NotConnectedException) {
                log_e(TAG, e.toString())
                e.printStackTrace()
                connect()
            } catch (e: InterruptedException) {
                log_e(TAG, e.toString())
                e.printStackTrace()
            }
        }
    }

之前一直使用 chat.send(message: String) 這個(gè)接口晕窑,在用spark 聊天的時(shí)候抑片,一直顯示我發(fā)送的是“廣播”,但我確實(shí)是只發(fā)給了一個(gè)人杨赤,也就是“發(fā)給一個(gè)人的廣播“蓝丙。這本身就是一個(gè)很怪的事情。而且這個(gè)“廣播”的丟失率還很高望拖。后面看了接口渺尘,發(fā)現(xiàn) chat.send(message: String) 默認(rèn)發(fā)送的是 normal類型的(normal為什么是廣播?)说敏,

后來改動(dòng)了一下鸥跟,好像改善了消息丟失的問題。

val stanza = Message()
stanza.body = message
stanza.type = Message.Type.chat
chat.send(stanza)

也只是修改了message 的 類型為 chat 而已。起碼 spark 里面不會(huì)顯示 廣播了医咨。

發(fā)送消息必須要先關(guān)注(訂閱)對(duì)方枫匾,不然的話發(fā)送不能成功。

添加好友

    /**
     * 添加好友 無分組
     * @param user_name jid
     * @param nick_name 用戶昵稱
     * @return 是否添加成功
     */
    fun addFriend(user_name: String, nick_name: String): Boolean {
        log_d(TAG, "addFriend")
        if (mConnection != null) {
            try {
                Roster.getInstanceFor(mConnection).createEntry(JidCreate.bareFrom(generateJID(user_name)),
                        nick_name, null)
                return true
            } catch (e: Exception) {
                log_e(TAG, e.toString())
                e.printStackTrace()
            }
        }
        return false
    }

添加好友到指定分組

    /**
     * 添加好友 加入到指定分組
     * @param user_name jid
     * @param nick_name 用戶昵稱
     * @param group_name 用戶組
     * @return 是否添加成功
     */
    fun addFriendToGroup(user_name: String, nick_name: String, group_name: String): Boolean {
        log_d(TAG, "addFriendToGroup")
        if (mConnection != null) {
            try {
                val subscription = Presence(Presence.Type.subscribe)
                subscription.to = JidCreate.entityBareFrom(generateJID(user_name))
                mConnection?.sendStanza(subscription)
                Roster.getInstanceFor(mConnection).createEntry(JidCreate.entityBareFrom(generateJID(user_name)),
                        nick_name,
                        arrayOf(group_name))

                return true
            } catch (e: Exception) {
                log_e(TAG, e.toString())
            }
        }
        return false
    }

獲取好友(Get All Friends)

    /**
     * 獲取所有好友信息
     * @return 所有好友列表
     */
    fun getAllFriends(): List<RosterEntry>? {
        log_d(TAG, "getAllFriends")
        if (mConnection != null) {
            val entryList = ArrayList<RosterEntry>()
            val rotryEntry = Roster.getInstanceFor(mConnection).entries
            entryList += rotryEntry
            return entryList
        }

        return null
    }

獲取好友列表

還封裝了好幾個(gè)接口

  • isAuthenticated() 判斷是否已經(jīng)登錄
  • isConnect() 判斷是否連接
  • disconnect() 斷開連接
  • getGroups() 獲取所有分組
  • getFriendsInGroup 獲取指定分組內(nèi)的所有好友
  • ... ... 等等

使用的時(shí)候拟淮,我用了 RxJava 的 鏈?zhǔn)秸{(diào)用干茉,用起來還不錯(cuò)很泊。

登錄
fun login(user_name: String, passwd: String) {
            log_d(TAG, "login name->$user_name")

            curUserName = user_name
            curPasswd = passwd
            if (mXmppApiManager.isAuthenticated()) {
                // 已經(jīng)登錄過
                val userName = mXmppApiManager.getAuthenticatedUser()
                if (!TextUtils.isEmpty(userName)) {
                    log_d(TAG, "account logined as -> " + userName!!)
                } else {
                    log_d(TAG, "login successful as -> " + user_name)
                }
            } else {
                if (!mXmppApiManager.isConnected()) {
                    // 先進(jìn)行連接

                    Observable.create(ObservableOnSubscribe<Boolean> { emitter ->
                        emitter.onNext(mXmppApiManager.connect())
                    })
                            .subscribeOn(Schedulers.io())
                            .flatMap { isConnectSuccessful ->
                                if (isConnectSuccessful) {
                                    // 連接成功后
                                    // 進(jìn)行注冊(cè)
                                    log_d(TAG, "xmpp connect successful")
                                    Observable.just(mXmppApiManager.registerUser(user_name, passwd))
                                } else {
                                    // 連接失敗
                                    log_d(TAG, "xmpp connect failed")
                                    // 幾秒后進(jìn)行重連
                                    handler.postDelayed(reconnectRunnable, RECONNECT_TIME_MILLSECOND)
                                    Observable.just(false)
                                }
                            }
                            .flatMap { isRegisterSuccessful ->
                                if (isRegisterSuccessful) {
                                    // 注冊(cè)成功
                                    // 進(jìn)行登錄
                                    log_d(TAG, "xmpp register successful")
                                    Observable.just(mXmppApiManager.login(user_name, passwd))
                                } else {
                                    // 注冊(cè)失敗
                                    log_d(TAG, "xmpp register failed")
                                    Observable.just(false)
                                }
                            }.observeOn(AndroidSchedulers.mainThread())
                            .subscribe({ isLoginSuccessful ->
                                if (isLoginSuccessful!!) {
                                    log_d(TAG, "login successful as -> " + mXmppApiManager.getAuthenticatedUser()!!)
                                } else {
                                    log_d(TAG, "login failed")
                                }
                            })
                } else {
                    // 直接進(jìn)行登錄操作
                    Observable.create(ObservableOnSubscribe<Boolean> { emitter ->
                        emitter.onNext(mXmppApiManager.registerUser(user_name, passwd))
                    })
                            .subscribeOn(Schedulers.io())
                            .flatMap { isRegisterSuccessful ->
                                if (isRegisterSuccessful) {
                                    // 注冊(cè)成功
                                    // 進(jìn)行登錄
                                    log_d(TAG, "xmpp register successful")
                                    Observable.just(mXmppApiManager.login(user_name, passwd))
                                } else {
                                    // 注冊(cè)失敗
                                    log_d(TAG, "xmpp register failed")
                                    Observable.just(false)
                                }
                            }.observeOn(AndroidSchedulers.mainThread())
                            .subscribe { isLoginSuccessful ->
                                if (isLoginSuccessful!!) {
                                    val userName = mXmppApiManager.getAuthenticatedUser()
                                    if (!TextUtils.isEmpty(userName)) {
                                        log_d(TAG, "account logined as -> " + userName!!)
                                    } else {
                                        log_d(TAG, "login successful as -> " + userName)
                                    }
                                } else {
                                    log_d(TAG, "login failed")
                                }
                            }
                }

            }
        }

在登錄的時(shí)候先進(jìn)行一些連接的判斷。

這樣使用的時(shí)候就不需要去處理是否連接的問題委造。

還有注冊(cè)也是一樣

        fun register(user_name: String, passwd: String) {
            // 如果已經(jīng)驗(yàn)證過的戳鹅,需要退出登錄?
            log_d(TAG, "register name->$user_name")

            if (!mXmppApiManager.isConnected()) {
                // 先進(jìn)行連接
                Observable.create(ObservableOnSubscribe<Boolean> { emitter ->
                    emitter.onNext(mXmppApiManager.connect())
                })
                        .subscribeOn(Schedulers.io())
                        .observeOn(Schedulers.io())
                        .flatMap { isConnectSuccessful ->
                            if (isConnectSuccessful) {
                                // 連接成功后
                                // 進(jìn)行注冊(cè)
                                log_d(TAG, "xmpp connect successful")
                                Observable.just(mXmppApiManager.registerUser(user_name, passwd))
                            } else {
                                // 連接失敗
                                log_d(TAG, "xmpp connect failed")
                                // 幾秒后進(jìn)行重連
                                handler.postDelayed(reconnectRunnable, RECONNECT_TIME_MILLSECOND)
                                Observable.just(false)
                            }
                        }
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe { isRegisterSuccessful ->
                            if (isRegisterSuccessful) {
                                // 注冊(cè)成功
                                // 進(jìn)行登錄
                                log_d(TAG, "xmpp register successful")
                            } else {
                                // 注冊(cè)失敗
                                log_d(TAG, "xmpp register failed")
                            }
                        }
            } else {
                // 直接進(jìn)行注冊(cè)操作
                Observable.create(ObservableOnSubscribe<Boolean> { emitter ->
                    emitter.onNext(mXmppApiManager.registerUser(user_name, passwd))
                })
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe { isRegisterSuccessful ->
                            if (isRegisterSuccessful) {
                                // 注冊(cè)成功
                                log_d(TAG, "xmpp register successful")
                            } else {
                                // 注冊(cè)失敗
                                log_d(TAG, "xmpp register failed")
                            }
                        }
            }
        }

這樣用起來就比較方便。

需要注意的問題

如何保持XMPP連接穩(wěn)定

遇到問題

應(yīng)用程序處于活動(dòng)狀態(tài)昏兆。但是遲早枫虏,XMPP連接都沒有任何提示。服務(wù)器表示客戶端仍處于聯(lián)機(jī)狀態(tài)爬虱,但未發(fā)送或者接收數(shù)據(jù)包隶债。

XMPPConnection connection.isConnected()返回 True。

實(shí)際上 客戶端 無法知道 實(shí)際連接已經(jīng)丟失跑筝。

解決方案
  1. 首先在 openfire 服務(wù)器后臺(tái)發(fā)現(xiàn)了這個(gè)

    服務(wù)器可以在斷開閑置連接前發(fā)送XMPP Ping請(qǐng)求給該客戶端死讹。客戶端必須回復(fù) Ping請(qǐng)求继蜡,這樣服務(wù)器能判斷客戶端連接確實(shí)是閑置狀態(tài)。 XMPP規(guī)范要求所有客戶端必須響應(yīng) Ping請(qǐng)求逛腿。如果客戶端不支持該P(yáng)ing請(qǐng)求稀并,必須返回錯(cuò)誤(這本身就是一個(gè)響應(yīng))。

    所以单默,我們的客戶端必須對(duì) 服務(wù)器的 ping 請(qǐng)求進(jìn)行回復(fù)碘举。但是 smack 4.2 的 incomingMessage 僅僅會(huì)返回 用戶消息,所以在 incomingmessage 回調(diào)里面沒有辦法完成這件事情搁廓。

    搜索一番之后發(fā)現(xiàn)一個(gè):

    connection = new XMPPTCPConnection(config);  
    PingManager pingManager = PingManager.getInstanceFor(connection); pingManager.setPingInterval(300);//seconds
    

    ?

    在 connect 完成后 加了這個(gè)設(shè)置引颈,測(cè)試了一下,仍然后斷線的問題境蜕,但是頻率少了蝙场。應(yīng)該是有一點(diǎn)作用的。

  2. 添加 XMPPConnectionListener 連接監(jiān)聽

    如果需要保持長(zhǎng)期的連接粱年,需要對(duì)很多異常進(jìn)行進(jìn)行處理售滤,也就是重連機(jī)制的實(shí)現(xiàn)。

    比如說

    • 監(jiān)聽到 connectitonCloseError 的時(shí)候
    • 監(jiān)聽到 connectionClose 的時(shí)候
    • 或者是連接異常的時(shí)候

    都可以加入 自動(dòng)重連的邏輯,從而保證 連接的穩(wěn)定性完箩。

  3. 添加 網(wǎng)絡(luò)變化 監(jiān)聽

    移動(dòng)應(yīng)用的網(wǎng)絡(luò)情況千變?nèi)f化赐俗,有時(shí)候并不穩(wěn)定,所以需要加入網(wǎng)絡(luò)情況的判斷

    class NetworkChangeReceiver: BroadcastReceiver() {
    
        private val TAG = NetworkChangeReceiver::class.java.simpleName
    
        override fun onReceive(context: Context?, intent: Intent?) {
            log_i(TAG, "onReceive 網(wǎng)絡(luò)狀態(tài)發(fā)生變化")
    
            // 如果api小于21弊知,getNetworkinfo(int networType) 已棄用
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                log_i(TAG, "API 小于 21")
    
                val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    
                // Wi-Fi 連接
                val wifiNetworkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
                // 移動(dòng)數(shù)據(jù)連接
                val dataNetworkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
    
                if (wifiNetworkInfo.isConnected && dataNetworkInfo.isConnected) {
                    log_i(TAG, "Wi-Fi 已連接阻逮,移動(dòng)數(shù)據(jù)已連接")
                    RxBus2.getIntanceBus().post(RxMessage(RxMessageConstants.MESSAGE_TYPE_NETWOKR, RxMessageConstants.MESSAGE_NETWORK_WIFI_CONNECTED) as Object)
                } else if (wifiNetworkInfo.isConnected && !dataNetworkInfo.isConnected) {
                    log_i(TAG, "Wi-Fi 已連接,移動(dòng)數(shù)據(jù)已斷開")
                    RxBus2.getIntanceBus().post(RxMessage(RxMessageConstants.MESSAGE_TYPE_NETWOKR, RxMessageConstants.MESSAGE_NETWORK_WIFI_CONNECTED) as Object)
                } else if (!wifiNetworkInfo.isConnected && dataNetworkInfo.isConnected) {
                    log_i(TAG, "Wi-Fi 已斷開秩彤,移動(dòng)數(shù)據(jù)已連接")
                    RxBus2.getIntanceBus().post(RxMessage(RxMessageConstants.MESSAGE_TYPE_NETWOKR, RxMessageConstants.MESSAGE_NETWORK_MOBILE_CONNECTED) as Object)
                } else {
                    RxBus2.getIntanceBus().post(RxMessage(RxMessageConstants.MESSAGE_TYPE_NETWOKR, RxMessageConstants.MESSAGE_NETWORK_DISCONNETED) as Object)
                    log_i(TAG, "Wi-Fi 已斷開叔扼,移動(dòng)數(shù)據(jù)已斷開")
                }
    
            } else {
                log_i(TAG, "API 大于 21")
    
                val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    
                val networks = connectivityManager.allNetworks
    
                var result = 0 // mobile false = 1, mobile true = 2 wifi = 4
    
                for (network in networks) {
                    val networkInfo = connectivityManager.getNetworkInfo(network)
    
                    networkInfo?.let {
                        //檢測(cè)到有數(shù)據(jù)連接,但是并連接狀態(tài)未生效呐舔,此種狀態(tài)為wifi和數(shù)據(jù)同時(shí)已連接币励,以wifi連接優(yōu)先
                        if (networkInfo.type == ConnectivityManager.TYPE_MOBILE && !networkInfo.isConnected) {
                            result += 1
                        }
    
                        //檢測(cè)到有數(shù)據(jù)連接,并連接狀態(tài)已生效珊拼,此種狀態(tài)為只有數(shù)據(jù)連接食呻,wifi并未連接上
                        if (networkInfo.type == ConnectivityManager.TYPE_MOBILE && networkInfo.isConnected) {
                            result += 2
                        }
    
                        //檢測(cè)到有wifi連接,連接狀態(tài)必為true
                        if (networkInfo.type == ConnectivityManager.TYPE_WIFI) {
                            result += 4
                         }
                   }
             }
              
              // 存在組合情況澎现,以組合相加的唯一值作為最終狀態(tài)的判斷
               when (result) {
                   0   ->  {
                       log_i(TAG, "Wi-Fi 已斷開仅胞,移動(dòng)數(shù)據(jù)已斷開")
                   }
                   2   ->  {
                       log_i(TAG, "Wi-Fi 已斷開,移動(dòng)數(shù)據(jù)已連接")
                   }
                   4   ->  {
                       log_i(TAG, "Wi-Fi 已連接剑辫,移動(dòng)數(shù)據(jù)已斷開")
                   }
                   5   ->  {
                       log_i(TAG, "Wi-Fi 已連接干旧,移動(dòng)數(shù)據(jù)已連接")
                   }
               }
           }
    
       }
    }           
    

在網(wǎng)絡(luò)變化的時(shí)候,進(jìn)行連接判斷妹蔽,或者重連操作椎眯,也是一種保持穩(wěn)定性的方法。

使用的第三方庫(kù)

compile "org.igniterealtime.smack:smack-android-extensions:4.2.0"
compile "org.igniterealtime.smack:smack-tcp:4.2.0"
compile "org.igniterealtime.smack:smack-extensions:4.2.0"

// 只是在使用的時(shí)候 方便一些
compile 'io.reactivex.rxjava2:rxjava:2.1.8'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
  • 可以根據(jù)網(wǎng)絡(luò)狀況自動(dòng)重連
  • 可以在中斷后自動(dòng)進(jìn)行重連

代碼在這里
已發(fā)布至Jimbray

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胳岂,一起剝皮案震驚了整個(gè)濱河市编整,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乳丰,老刑警劉巖掌测,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異产园,居然都是意外死亡汞斧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門什燕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘勒,“玉大人,你說我怎么就攤上這事屎即≈僖澹” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)埃撵。 經(jīng)常有香客問我赵颅,道長(zhǎng),這世上最難降的妖魔是什么暂刘? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任饺谬,我火速辦了婚禮,結(jié)果婚禮上谣拣,老公的妹妹穿的比我還像新娘募寨。我一直安慰自己,他們只是感情好森缠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布拔鹰。 她就那樣靜靜地躺著,像睡著了一般贵涵。 火紅的嫁衣襯著肌膚如雪列肢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天宾茂,我揣著相機(jī)與錄音瓷马,去河邊找鬼。 笑死跨晴,一個(gè)胖子當(dāng)著我的面吹牛欧聘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播端盆,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼怀骤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了焕妙?” 一聲冷哼從身側(cè)響起蒋伦,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎访敌,沒想到半個(gè)月后凉敲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衣盾,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寺旺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了势决。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阻塑。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖果复,靈堂內(nèi)的尸體忽然破棺而出陈莽,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布走搁,位于F島的核電站独柑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏私植。R本人自食惡果不足惜忌栅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望曲稼。 院中可真熱鬧索绪,春花似錦、人聲如沸贫悄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窄坦。三九已至唤反,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫡丙,已是汗流浹背拴袭。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曙博,地道東北人拥刻。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像父泳,于是被迫代替她去往敵國(guó)和親般哼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一惠窄、Smack庫(kù)概述 ????Smack是一個(gè)開源蒸眠、易用的XMPP/Jabber客戶端庫(kù),它使用Java語言開發(fā)杆融,...
    AndryYu閱讀 6,128評(píng)論 2 13
  • 不好意思楞卡,內(nèi)附一些swift的東西地址:https://github.com/tanzhiwen/SwiftTip...
    小人不才閱讀 2,213評(píng)論 1 7
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)脾歇,斷路器蒋腮,智...
    卡卡羅2017閱讀 134,664評(píng)論 18 139
  • 關(guān)于XMPP最權(quán)威的講解:http://www.jabbercn.org/RFC3920(這個(gè)才是最權(quán)威的,下面文...
    隨風(fēng)飄蕩的小逗逼閱讀 1,490評(píng)論 1 5
  • 中秋過后藕各,就是十一池摧,然后再經(jīng)過冗長(zhǎng)平靜的11月后,我們就會(huì)迎來圣誕節(jié)激况,迎來元旦作彤,迎來新年膘魄。元旦的時(shí)候每個(gè)人又要開始...
    花滿樓閱讀 396評(píng)論 0 2