一、前提
- 源码是smack4.1.8
- 具体可以看下我之前分装支持单聊的代码 https://github.com/YuanClouds/SnakeXMPP
二、Roster管理(一)
(一)Roster 初始化
(1) 上层实例化Roster
mRoster = Roster.getInstanceFor(this.mConnection);
//getInstanceFor源码 单利模式创建了roster,而且与当前实例化的connection进行绑定关系
public static synchronized Roster getInstanceFor(XMPPConnection connection) {
Roster roster = INSTANCES.get(connection);
if (roster == null) {
roster = new Roster(connection);
INSTANCES.put(connection, roster);
}
return roster;
}
(二)获取好友花名册
(1) Roster默认获取
初始化Roster的时候,roseter会主动reload一次花名册,可以看下里面的构造方法
private Roster(final XMPPConnection connection) {
super(connection);
//...
// Listen for connection events
connection.addConnectionListener(new AbstractConnectionClosedListener() {
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
//.. 如果登陆成功,会主动获取一次花名册...
try {
Roster.this.reload();
}
//...
}
@Override
public void connectionTerminated() { //... }
});
//..
}
// reload 实际是发送了一个RosterPacket packet,默认是quert entry
public void reload() throws NotLoggedInException, NotConnectedException{
// RosterPacket packet默认是query操作,即获取当前session用户下面的花名册
RosterPacket packet = new RosterPacket();
if (rosterStore != null && isRosterVersioningSupported()) {
packet.setVersion(rosterStore.getRosterVersion());
}
rosterState = RosterState.loading; //注意这里的状态值...
connection.sendIqWithResponseCallback(//...);
}
这里有一个比较值得学习的写法,获取到花名册后,Roster该怎么进行回调?这里我们关键看下sendIqWithResponseCallback和RosterResultListener立的关键代码
sendIqWithResponseCallback是一个接口方法,具体由抽象基类AbstractXMPPConnection实现,进一步追踪sendIqWithResponseCallback
首先
我们要关注的还是Roster的构建方法里面,关键代码如下
connection.registerIQRequestHandler(new RosterPushListener());
registerIQRequestHandler是XmppConnect中注册IQ包数据的回调,这里允许业务者自己添加回调并且自己处理解析业务。RosterPushListener则是Roster监听服务器返回的set类型的IQ包数据后进行解析的操作。具体读者可以自己阅读下,具体就是解析后的得到addedEntries, updatedEntries, deletedEntries,并且调用fireRosterChangedEvent方法将所有监听entry的监听器回调新的状态给上层
接着
进一步分析sendIqWithResponseCallback,首先这里要分为两个步骤。第一,从前面的registerIQRequestHandler,当connect发送一个packet至服务器后,返回的IQ包,如果是Roster相关的IQ包,会由RosterPushListener进行解析处理,如果entry有状态变化(被删除、新增等),进行状态更新会返回一个packet。第二,我们注意到调用sendIqWithResponseCallback会传入一个StanzaListener,这里主要处理返回的packet。当然这里的packet就是第一点registerIQRequestHandler的RosterPushListener处理后的packet,这里的RosterPushListener算是一个预先状态更新,例如断点的东西。
最后
是关于回调给上层获取到花名册信息,有两种主要方式
- RosterLoadedListener监听
这是针对初始化Roster后,登陆成功后自动获取花名册的回调 - RosterEntries监听,调用mRoster.reloadAndWait()后load结束后会回调,属于主动向服务器获取花名册
其实两种都是一样的逻辑,send一个rosterPacket,下面分析下这两个回调
(a) Roster默认获取RosterLoadedListener
这个比较好理解,刚刚前面分析sendIqWithResponseCallback后由RosterPushListener进行packet处理后,贴下最后的代码
try {
synchronized (rosterLoadedListeners) {
for (RosterLoadedListener rosterLoadedListener : rosterLoadedListeners) {
rosterLoadedListener.onRosterLoaded(Roster.this);
}
}
}
(b)RosterEntries监听
这里比较有意思,其实smack里面大量运用了同步锁(防止多线程并发带来的问题),首先上层调用mRoster.reloadAndWait(),然后调用mRoster.getEntriesAndAddListener(this, this)后便可以回调到RosterEntries监听,这里贴下源码
public void reloadAndWait() throws NotLoggedInException, NotConnectedException, InterruptedException {
reload();
waitUntilLoaded();
}
reload这里就不说明了,waitUntilLoaded这里是比较有意思处理,先贴下
protected boolean waitUntilLoaded() throws InterruptedException {
long waitTime = connection().getPacketReplyTimeout();
long start = System.currentTimeMillis();
while (!isLoaded()) {
if (waitTime <= 0) {
break;
}
synchronized (this) {
if (!isLoaded()) {
wait(waitTime);
}
}
long now = System.currentTimeMillis();
waitTime -= now - start;
start = now;
}
return isLoaded();
}
首先我们在网络请求的时候一般来说都是有一个网络请求超时时间。这里在异步执行了reload,开启了当前线程等待状态,等待的终止有两种可能:
第一是reload到数据了,isLoaded的状态为true,自然就退出了阻塞。
第二是当waitTime等待到超时时间了,wait方法会主动抛出InterruptedException异常,强制停止了整个reload,因此也不会回调到RosterEntries监听,如果reload成功,即马上调用mRoster.getEntriesAndAddListener(this, this)方法,注入新的RosterEntries监听,即可以获取到entry,看下getEntriesAndAddListener源码
public void getEntriesAndAddListener(RosterListener rosterListener, RosterEntries rosterEntries) {
Objects.requireNonNull(rosterListener, "listener must not be null");
Objects.requireNonNull(rosterEntries, "rosterEntries must not be null");
synchronized (rosterListenersAndEntriesLock) {
rosterEntries.rosterEntires(entries.values()); // 这里回掉给上层
addRosterListener(rosterListener);
}
}
三、最后
其他就是上层自己解析Entry了,最后分享下获取好友花名册IQ包
SENT (0):
RECV (0): 我的好友 我的好友