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) } }
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() } }
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() } }
调用某个类使用某一功能,只需要:
//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() }
③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类设置为单例模式上。取消单例模式就好了。