Smack实现添加好友

代码说明

为了方便以后的写作,新建一个包,专门用来存放Smack实现的功能,比如:

 

其中,SmackConnection,SmackLogin和SmackAddUser三个类是前面实现过的,分别是:

备注:这里除了SmackConnection之外,另外两个类不要设置成单例模式了,没有必要,甚至在之后的使用中会出错。但因为这只是一个demo,所以也就没有修改了。

class SmackConnection private constructor(){
    //单例模式
    companion object{
        val smackConnection by lazy{
            SmackConnection()
        }
    }
    fun conn(): XMPPTCPConnection {
        val addr: InetAddress = InetAddress.getByName("169.254.235.122")
        val config: XMPPTCPConnectionConfiguration = XMPPTCPConnectionConfiguration.builder()
//            .setUsernameAndPassword(username,password)
            .setXmppDomain("169.254.235.122")
            .setHost("169.254.235.122")
            .setHostAddress(addr)
            .setPort(5222)
            .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
//            .setSendPresence(true)
//            .setDebuggerEnabled(true)
            .build()
        return XMPPTCPConnection(config)
    }
    //
    fun conn(username:String,password:String): XMPPTCPConnection {
        val addr: InetAddress = InetAddress.getByName("169.254.235.122")
        val config: XMPPTCPConnectionConfiguration = XMPPTCPConnectionConfiguration.builder()
            .setUsernameAndPassword(username,password)
            .setXmppDomain("169.254.235.122")
            .setHost("169.254.235.122")
            .setHostAddress(addr)
            .setPort(5222)
            .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
//            .setSendPresence(true)
//            .setDebuggerEnabled(true)
            .build()
        return XMPPTCPConnection(config)
    }
}
View Code
class SmackLogin private constructor(conn:XMPPTCPConnection){
    private var conn:XMPPTCPConnection?=null
    //为了避免多次连接,也许给类传入一个XMPPTCPConnection参数比较好。这样不用在类的内部进行连接操作,而
    //只在外部连接就好了
    //实现带参数的单例模式@see https://www.jianshu.com/p/d2bbe7b66d63
    init{
        this.conn=conn
    }
    companion object{
        var instance:SmackLogin?=null
        fun getInstance(conn:XMPPTCPConnection):SmackLogin{
            if(instance==null){
                synchronized(SmackLogin::class){
                    if(instance==null){
                        instance= SmackLogin(conn)
                    }
                }
            }
            return instance!!
        }
    }
    fun login(username:String,password:String){
        Thread{
            kotlin.run {
                try{
                    conn!!.connect()
                    conn!!.login(username,password)
                    println("登录成功...")
                }catch (e:Exception){
                    println("登录错误...")
                    e.printStackTrace()
                }

            }
        }.start()
    }
}
View Code
class SmackAddUser private constructor(){
    companion object{
        val smackAddUser by lazy{
            SmackAddUser()
        }
    }
     fun addUser(username: String, password: String) {
        Thread{
            kotlin.run {
                try{
                    val conn: XMPPTCPConnection = SmackConnection.smackConnection.conn()
                    conn.connect()
                    val accountManager: AccountManager = AccountManager.getInstance(conn)
                    accountManager.sensitiveOperationOverInsecureConnection(true)
                    accountManager.createAccount(Localpart.from(username),password)
                }catch (e:Exception){
                    println("添加用户失败")
                    e.printStackTrace()
                }

            }
        }.start()

    }
}
View Code

调用某个类使用某一功能,只需要:

//SmackAddUser.smackAddUser.addUser("张三","abc123123")
//var conn: XMPPTCPConnection = SmackConnection.smackConnection.conn()
// SmackLogin.getInstance(conn).login("张三","abc123123")

 (

这样显得复杂了一些,但是之后会比较方便吧~

)

参考文档

好友管理使用Roster类,参考文档位于/javadoc/org/jivesoftware/smack/roster/Roster.html

有几个后面可能会用到的方法:

①获得连接用户的roster实例

public static Roster getInstanceFor​(XMPPConnection connection)

②添加好友

createEntry​(BareJid user, String name, String[] groups),已被废弃,使用createItemAndRequestSubscription替代:

void createItemAndRequestSubscription​(BareJid jid, String name, String[] groups)

③获得好友列表

public Set<RosterEntry> getEntries()

③创建分组

RosterGroup createGroup​(String name)

使用Roster管理好友之前需要先连接服务器,登录

代码

首先添加一些账号,比如现在有几个用户了

 

 实现方法一:

        val addr: InetAddress = InetAddress.getByName("169.254.235.122")
        val config: XMPPTCPConnectionConfiguration = XMPPTCPConnectionConfiguration.builder()
            .setUsernameAndPassword("admin","yourPassword")
            .setXmppDomain("169.254.235.122")
            .setHost("169.254.235.122")
            .setHostAddress(addr)
            .setPort(5222)
            .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
//            .setSendPresence(true)
//            .setDebuggerEnabled(true)
            .build()
        var conn= XMPPTCPConnection(config)
        Thread{
            kotlin.run {
                conn.connect().login()
                var roster= Roster.getInstanceFor(conn)
                    //添加好友
                try{
                    roster.createEntry(JidCreate.bareFrom("vocus"),null,null)
                }catch (e:Exception){
                    println("添加好友失败")
                    e.printStackTrace()
                }
                //返回值为空的解决
                //@see https://stackoverflow.com/questions/7170161/problem-with-smack-in-facebook-chat-app-for-android-connection-getroster-gete
                if (!roster.isLoaded) try {
                    roster.reloadAndWait()
                } catch (e: NotLoggedInException) {
                    e.printStackTrace()
                } catch (e: NotConnectedException) {
                    e.printStackTrace()
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
                var list=roster.entries
                println("好友:${list.size}")
                var iterator:Iterator<RosterEntry> =list.iterator()
                while (iterator.hasNext()){
                    println("好友${iterator.next()}")
                }
            }
        }.start()

实现方法二:

先创建SmackRosterManager类

class SmackRosterManager private constructor(conn:XMPPTCPConnection){
    companion object{
        var instance:SmackRosterManager?=null
        fun getInstance(conn: XMPPTCPConnection):SmackRosterManager{
            if(instance==null){
                synchronized(SmackRosterManager::class){
                    if(instance==null){
                        instance= SmackRosterManager(conn)
                    }
                }
            }
            return instance!!
        }
    }
    private var conn:XMPPTCPConnection?=null
    private var roster:Roster?=null
    init{
        //this.conn=conn
        //获得连接用户的roster对象
        this.roster= Roster.getInstanceFor(conn)
    }
    //创建分组
    fun createGroup(groupName:String){
        roster!!.createGroup(groupName)
    }
    //获得好友列表
    fun getAllFriends(){
        if (!roster!!.isLoaded) try {
            roster!!.reloadAndWait()
        } catch (e: SmackException.NotLoggedInException) {
            e.printStackTrace()
        } catch (e: SmackException.NotConnectedException) {
            e.printStackTrace()
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }
        var friends=roster!!.entries
        println("好友~${friends.size}")
        for(f in friends){
            println("好友~$f")
        }
    }
    //添加好友
    fun addFriend(username:String){
        roster!!.createEntry(JidCreate.bareFrom(username),null,null)
    }

}

调用类方法添加好友和获得好友列表:

        var conn: XMPPTCPConnection = SmackConnection.smackConnection.conn()
        //SmackLogin.getInstance(conn).login("admin","yourPassword")
//        Thread{
//            kotlin.run {
//                conn.connect().login()
//                SmackRosterManager.getInstance(conn).getAllFriends()
//            }
//        }.start()
        SmackLogin.getInstance(conn).login("admin", "yourPassword")
        //这里的主要问题是登录是在另外一个线程里进行的,那这里是否也可以判断登录是否完成,如果未完成则等待登录完成再继续之后的操作?
        //也就是一个设置精确的睡眠时间的问题
        //但,我以为一般的app登陆完成之后才能跳转新的activity,就不需要让线程睡眠了。所以这里使用睡眠只是临时测试的,正常项目里是不需要吧

        Thread.sleep(2000)
        //添加好友
        SmackRosterManager.getInstance(conn).addFriend("vocus")
        SmackRosterManager.getInstance(conn).addFriend("xiaomayi")
        SmackRosterManager.getInstance(conn).addFriend("张三")
        //获得好友列表
        SmackRosterManager.getInstance(conn).getAllFriends()

运行结果:

 

 

 

 遇到的问题

用户有好友,但获得好友列表为空

①用户登录还没有完成

可能登录在别的线程中进行的,在输出好友列表之前,用户没有登录完成。这个时候需要调用Thread.Sleep让线程睡眠几秒。在实际开发中,是等待用户登录完成才跳转到新的activity中的,所以应该不用担心这个问题

②服务器发过来数据之前,输出列表的代码已经被调用(?)

那么在输出列表之前可以判断roster是否加载完成,当未加载完成的时候,等待其加载,加载完成之后再输出:

if (!roster!!.isLoaded) try {
            roster!!.reloadAndWait()
        } catch (e: SmackException.NotLoggedInException) {
            e.printStackTrace()
        } catch (e: SmackException.NotConnectedException) {
            e.printStackTrace()
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }

参考:Problem with Smack in Facebook chat app for Android: Connection.getRoster().getEntries() is always empty

③BareJid 类型

createEntry​(BareJid user, String name, String[] groups)第一个参数是BareJid,可以使用JidCreate.bareFrom(string!)将字符串转为BareJid 类型

④未完成的功能

与添加好友相关的还有创建分组,删除好友;接收用户好友请求信息,同意或拒绝好友申请请求。这些之后再说吧

处理一个bug

关闭app,重新打开的时候,提示一些错误:

①org.jivesoftware.smack.SmackException$AlreadyConnectedException: Client is already connected

已经连接了,再次连接会出现该错误提示。因为关闭应用之后,连接并未断开。可以在MainActivity的声明周期函数onDestroy()中调用conn!!.disconnect(),断开连接;或者也可以在连接之前判断使用conn!!.isConnected判断一下是否已存在连接,如果不存在再建立连接。

②org.jivesoftware.smack.SmackException$NotLoggedInException: Client is not logged in 

Roster的很多方法会抛出NotLoggedInException异常,原因是没有登录。这个问题竟然出来我把SmackLogin类设置为单例模式上。取消单例模式就好了。

posted @ 2020-12-31 08:22  vocus  阅读(460)  评论(0编辑  收藏  举报