美文网首页
Android 基于smack的Xmpp聊天室

Android 基于smack的Xmpp聊天室

作者: 薰舞空 | 来源:发表于2020-01-07 11:19 被阅读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了。


    相关文章

      网友评论

          本文标题:Android 基于smack的Xmpp聊天室

          本文链接:https://www.haomeiwen.com/subject/fvqractx.html