xmpp之smack Roster源码分析(一)

一、前提

  • 源码是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): 我的好友我的好友

感谢阅读

你可能感兴趣的:(xmpp之smack Roster源码分析(一))