Android 基于smack的Xmpp聊天室

之前用的smack版本早,也有些问题,这段重新整理了下

大致的功能点:
1.基础配置
2.聊天建立
3.离线消息、流管理


基于kotlin写的,不过区别也不是很大,方法都一样


引用库为:

    implementation "org.igniterealtime.smack:smack-android-extensions:4.3.4"
    implementation "org.igniterealtime.smack:smack-tcp:4.3.4"



首先配置基础信息:

val config = XMPPTCPConnectionConfiguration.builder()
                .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)//安全模式认证
                .setXmppDomain(host)
                .enableDefaultDebugger()
                .setConnectTimeout(10_000)
                .build()
connection = XMPPTCPConnection(config)



setXmppDomain放域名,enableDefaultDebugger是debug模式,方便调试,安全模式那个暂时按关闭


然后建立监听:

        //ConnectionListener
        connection.addConnectionListener(XmppConnectionListener())
        //ReceiveListener
        connection.addSyncStanzaListener(XmppReceiveListener(), StanzaFilter { true })
        //SendListener
        connection.addStanzaSendingListener(XmppSendListener(), StanzaFilter { true })



这三个分别是连接状态监听、接受包监听、发送包监听,后两个里的StanzaFilter是过滤器,有什么需要可以在里面定制


XmppConnectionListener就是连接状态,实现接口ConnectionListener,新版的ConnectionListener里只有四个方法,如下:

    void connected(XMPPConnection connection);
    void authenticated(XMPPConnection connection, boolean resumed);
    void connectionClosed();
    void connectionClosedOnError(Exception e);



其它三个没什么好说的,connectionClosedOnError注意一下,账号互踢那个就是在这里回调

if (e?.message?.contains("conflict") == true) {
      //互踢
}

这样判断就ok



XmppReceiveListener和XmppSendListener都是实现StanzaListener接口,就一个方法

void processStanza(Stanza packet) 



Stanza这个类就代表消息的对象,里面的结构不算复杂,如果想看看内容用toString就行



基础配置完成后,还有比较重要的一步,就是自动重连机制,关键类ReconnectionManager,方法如下:

            // 允许自动连接
            val reconnectionManager = ReconnectionManager.getInstanceFor(connection)
            // 重联间隔5秒
            reconnectionManager.setFixedDelay(5)
            reconnectionManager.enableAutomaticReconnection()//开启重联机制



setFixedDelay单位是秒


完成这步配置之后,如果联通xmpp,然后断网再联网,正常情况XmppConnectionListener中会依次回调
connectionClosedOnError、connected


这样就重连成功了,如果是已经通过登录验证的,还会回调authenticated,配置完自动重连是无需再重新登录的


后面还可以配置ping,代码如下:

            // 维持ping
            PingManager.setDefaultPingInterval(10)
            val pingManager = PingManager.getInstanceFor(connection)
            // 监听连接状态
            pingManager.registerPingFailedListener(PingListener())



PingListener实现接口PingFailedListener,里面只有一个pingFailed方法,ping失败会回调


到此为止基础配置和监听就完成了,后面就是建立连接与登录认证,方法如下:

    //连接
    connection.connect()
    //登录
    connection.login(name, pwd, Resourcepart.from(resource))



其中login里的name参数要注意,格式大概是类似123@xxx.com这样,只写id不行

最后resource参数意义类似空间,同一处资源空间内,账号不能重复登录,不同空间则可以

另外注意,这两个都是耗时操作,不能放在UI线程,并且遇到异常会抛出错误



与这两个方法相对的状态判断是connection.isConnected与connection.isAuthenticated


最终登录方法大致如下:

  fun login(): Boolean {
        if (isAuthenticated()) {
            return true
        }

        if (!isConnected()) {
            try {
                connection.connect()
            } catch (e: Exception) {
                XmppLogUtils.logE("on connect $e")
            }
        }

        if (!isConnected()) {
            return false
        }

        try {
            connection.login(name, pwd, Resourcepart.from(resource))
        } catch (e: Exception) {
            XmppLogUtils.logE("on login $e")
        }

        return isAuthenticated()
    }



当isAuthenticated返回true,同时authenticated被调用时,就完成了整个连接和登录验证过程


聊天就很简单了,关键类ChatManager,参数需要对方的id,方法如下:

                val chatManager = ChatManager.getInstanceFor(connection)
                val jid = JidCreate.entityBareFrom(id)
                val chat = chatManager.chatWith(jid)



这样就有了一个聊天的实例,然后通过addOutgoingListener和addIncomingListener监控消息动向

                chatManager.addOutgoingListener(ChatSendListener())
                chatManager.addIncomingListener(ChatReceiveListener())



发消息则是用chat的send方法

      chat.send(msg)



msg既可以直接传字符串,也可以创建Message对象

        Message stanza = new Message();
        stanza.setBody(message);
        stanza.setType(Message.Type.chat);
        send(stanza);



简单的测试可以自己和自己聊,OutgoingListener监控到发出,IncomingListener监控受到,就代表整个流程ok了,退出的话用disconnect就可以



因为前面我们已经配置了自动重连,在中途断线的情况下,消息基本是不会丢失的,但是如果处于未连接状态时,有人发送了一些消息,那么再登录时,是接收不到的,这类的消息就是离线消息


smack本身提供了用于获取离线消息的类OfflineMessageManager,不知道为啥我用这个不行,可能是服务端没开启配置,不过方法还是在此记录一下

首先要在之前配置连接的时候,加一个setSendPresence(false)方法,简单可以理解为以离线状态登录

      val config = XMPPTCPConnectionConfiguration.builder()
                .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)//安全模式认证
                .setXmppDomain(host)
                .enableDefaultDebugger()
                .setConnectTimeout(10_000)
                .setSendPresence(false)
                .build()



然后在登录认证通过后,处理离线消息

     val offlineManager = OfflineMessageManager(connection)
        try {
            XmppLogUtils.logX("supportsFlexibleRetrieval: " + offlineManager.supportsFlexibleRetrieval())
            XmppLogUtils.logX("离线消息数量: " + offlineManager.messageCount)

            val it = offlineManager.messages
            
            for (item in it) {
                XmppLogUtils.logX("离线: " + item.body)
            }

            offlineManager.deleteMessages()

            connection.sendStanza(Presence(Presence.Type.available))

        } catch (e: Exception) {
            XmppLogUtils.logE("offlineManager $e")
            e.printStackTrace()
        }

步骤很简单,第一步用connection构建一个OfflineMessageManager实例,然后处理offlineManager.messages这些离线消息,处理完了之后用deleteMessages清空这些消息,不然下次还有,最后sendStanza(Presence(Presence.Type.available))表示上线

前面也说了,可能是由于服务器配置问题,这个方法在我这不好使,所以用了另一种方法,也就是流管理


流管理就是xep-0198号协议,有兴趣的自己查查,整体内容比较多,就不详细介绍了,只说用法


首先通过connection开启,要在连接之前配置

 connection.setUseStreamManagement(true)



然后,在登录成功后,向服务器发送相关消息

        val resumed = connection.streamWasResumed()
        val enabled = connection.isSmEnabled

        XmppLogUtils.logX("SM: " + resumed + " " + enabled)

        if (resumed || enabled) {
            try {
                connection.sendSmAcknowledgement()
            } catch (e: StreamManagementException.StreamManagementNotEnabledException) {
                e.printStackTrace()
            } catch (e: SmackException.NotConnectedException) {
                e.printStackTrace()
            }

        } else {
            try {
                connection.sendNonza(StreamManagement.Enable(true))
            } catch (e: SmackException.NotConnectedException) {
                e.printStackTrace()
            }

        }

        connection.sendStanza(Presence(Presence.Type.available))

简单两步就ok了

配置完可以试试,这时就能收到离线消息了,而且相比OfflineMessageManager更不容易丢消息,因为有一些特殊情况,没收到的消息不会被判断为离线消息,就像A掉线的瞬间,如果服务器没有及时确认A已经离线,就不会把发给A的消息认定为离线消息,这样OfflineMessageManager里是没有的,但通过xep-0198就能收到这类的消息,具体实现机制自行了解去。


后面一些消息去重,消息回执,时效性判断之类的,就自行处理了,跟主线无关,到此基本的xmpp使用就OK了。



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358