Android 基于smack的Xmpp聊天室

2020-01-07  本文已影响0人  薰舞空

之前用的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了。


上一篇 下一篇

猜你喜欢

热点阅读