Android基于XMPP协议之asmack源码分析

来源:互联网 发布:mac修容 编辑:程序博客网 时间:2024/05/29 18:50

一、整体聊天功能实现的流程

1>与openfire服务器建立连接2>获取连接对象,建立输入输出流3>开启输入数据流,线程阻塞等待消息,发出消息4>关闭输入输出流,关闭连接

二、解析连接服务器的类XmppConnection

首先 XmppConnection调用connect方法连接服务器,这里调用connectUsingConfiguration(config)方法,这里我们看到了私有变量config,这里应该初始化了系统的配置信息,这里在初始化的时候调用了mConfiguration = new ConnectionConfiguration(HOST, PORT);下面接着看connectUsingConfiguration方法;

  public void connect() throws XMPPException {        // Establishes the connection, readers and writers        connectUsingConfiguration(config);        // Automatically makes the login if the user was previously connected successfully        // to the server and the connection was terminated abruptly        if (connected && wasAuthenticated) {            // Make the login            if (isAnonymous()) {                // Make the anonymous login                loginAnonymously();            }            else {                login(config.getUsername(), config.getPassword(), config.getResource());            }            notifyReconnection();        }    }

我们看到,这里获取的 this.socket存于XmppConnection中,每个XmppConnection对应的当前设备和服务器的链接,对应的一个socket对象,缓存于当前XmppConnection中,便于使用,这里新建socket连接后做了一个初始化连接的操作,即initConnection(),我们继续看这个操作

private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException {        Iterator<HostAddress> it = config.getHostAddresses().iterator();        List<HostAddress> failedAddresses = new LinkedList<HostAddress>();        boolean xmppIOError = false;        while (it.hasNext()) {            exception = null;            HostAddress hostAddress = it.next();            String host = hostAddress.getFQDN();            int port = hostAddress.getPort();            try {                if (config.getSocketFactory() == null) {                    this.socket = new Socket(host, port);                }                else {                    this.socket = config.getSocketFactory().createSocket(host, port);                }            } catch (UnknownHostException uhe) {                String errorMessage = "Could not connect to " + host + ":" + port + ".";                exception = new XMPPException(errorMessage, new XMPPError(XMPPError.Condition.remote_server_timeout,                        errorMessage), uhe);            } catch (IOException ioe) {                String errorMessage = "XMPPError connecting to " + host + ":" + port + ".";                exception = new XMPPException(errorMessage, new XMPPError(XMPPError.Condition.remote_server_error,                        errorMessage), ioe);                xmppIOError = true;            }        //此处省略部分代码        socketClosed = false;        initConnection();    }

新建连接的socket后这里一开始便有一个initReaderAndWriter方法,这里初始化字符输入输出流, 这里创建了 packetWriter和packetReader ,用于向服务器发送和接收消息,当然下面对应的有程序异常后对于输入输出流和socket的关闭的操作,我们来看看packetWriter这个向服务器发送消息的类具体做了什么操作

 private void initConnection() throws XMPPException {        boolean isFirstInitialization = packetReader == null || packetWriter == null;        compressionHandler = null;        serverAckdCompression = false;        initReaderAndWriter();        try {            if (isFirstInitialization) {                packetWriter = new PacketWriter(this);                packetReader = new PacketReader(this);                // If debugging is enabled, we should start the thread that will listen for                 if (config.isDebuggerEnabled()) {                    addPacketListener(debugger.getReaderListener(), null);                    if (debugger.getWriterListener() != null) {                        addPacketSendingListener(debugger.getWriterListener(), null);                    }                }            }            else {                packetWriter.init();                packetReader.init();            }            packetWriter.startup();            packetReader.startup();            connected = true;            if (isFirstInitialization) {                // Notify listeners that a new connection has been established                for (ConnectionCreationListener listener : getConnectionCreationListeners()) {                    listener.connectionCreated(this);                }            }        }        catch (XMPPException ex) {            // An exception occurred in setting up the connection. Make sure we shut down the            // readers and writers and close the socket.            if (packetWriter != null) {                try {                    packetWriter.shutdown();                }                catch (Throwable ignore) { /* ignore */ }                packetWriter = null;            }            if (packetReader != null) {                try {                    packetReader.shutdown();                }                catch (Throwable ignore) { /* ignore */ }                packetReader = null;            }            if (reader != null) {                try {                    reader.close();                }                catch (Throwable ignore) { /* ignore */ }                reader = null;            }            if (writer != null) {                try {                    writer.close();                }                catch (Throwable ignore) {  /* ignore */}                writer = null;            }            if (socket != null) {                try {                    socket.close();                }                catch (Exception e) { /* ignore */ }                socket = null;            }            this.setWasAuthenticated(authenticated);            chatManager = null;            authenticated = false;            connected = false;            throw ex;        // Everything stoppped. Now throw the exception.        }    }

三、PacketWriter(发送消息)

这里新建了一个消息队列,用于存放用户将要发送的消息,存放于这个消息列队中,然后初始化获取用户将要发送消息的接收过程init(),这里开启了一个线程writerThread调用writePackets方法,这个writerThread线程的开启在PacketWriter的startUp方法中,下面我们看下writePackets做了什么操作

 protected PacketWriter(XMPPConnection connection) {        this.queue = new ArrayBlockingQueue<Packet>(500, true);        this.connection = connection;        init();    }protected void init() {        this.writer = connection.writer;        done = false;        writerThread = new Thread() {            public void run() {                writePackets(this);            }        };        writerThread.setName("Smack Packet Writer (" + connection.connectionCounterValue + ")");        writerThread.setDaemon(true);    }

这里一开便调用了openStream,这里添加字符串去打开xmpp协议指定的输入输出流,以便于后面能发送数据,然后这里有一个while循环,循环的状态基本为阻塞状态,nextPacket去用户那里获取即将要发送的消息我们等会再看,如果拿到的消息不为空,则 writer.write(packet.toXML());直接传输到服务器,中间有一个 queue.clear();表示消息发送完成后清楚消息队列 writer.write(“”)结束消息的标志;完成后关闭输入输出流,接下来我们看下nextPacket方法具体的操作

  void openStream() throws IOException {        StringBuilder stream = new StringBuilder();        stream.append("<stream:stream");        stream.append(" to=\"").append(connection.getServiceName()).append("\"");        stream.append(" xmlns=\"jabber:client\"");        stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");        stream.append(" version=\"1.0\">");        writer.write(stream.toString());        writer.flush();    } private void writePackets(Thread thisThread) {        try {            // Open the stream.            openStream();            // Write out packets from the queue.            while (!done && (writerThread == thisThread)) {                Packet packet = nextPacket();                if (packet != null) {                    writer.write(packet.toXML());                    if (queue.isEmpty()) {                        writer.flush();                    }                }            }            // Flush out the rest of the queue. If the queue is extremely large, it's possible            // we won't have time to entirely flush it before the socket is forced closed            // by the shutdown process.            try {                while (!queue.isEmpty()) {                    Packet packet = queue.remove();                    writer.write(packet.toXML());                }                writer.flush();            }            catch (Exception e) {                e.printStackTrace();            }            // Delete the queue contents (hopefully nothing is left).            queue.clear();            // Close the stream.            try {                writer.write("</stream:stream>");                writer.flush();            }            catch (Exception e) {                // Do nothing            }            finally {                try {                    writer.close();                }                catch (Exception e) {                    // Do nothing                }            }        }        catch (IOException ioe) {            // The exception can be ignored if the the connection is 'done'            // or if the it was caused because the socket got closed            if (!(done || connection.isSocketClosed())) {                done = true;                // packetReader could be set to null by an concurrent disconnect() call.                // Therefore Prevent NPE exceptions by checking packetReader.                if (connection.packetReader != null) {                        connection.notifyConnectionError(ioe);                }            }        }    }

这里有一个循环,从消息队列中获取用户即将发送的消息,如果有,则从消息队列中取出来,没有则synchronized 同步中调用 queue.wait(),让当前的writerThread处于等待状态,等待用户将要发送的消息存入消息队列,这里我们看完了packetWriter,接下来我们看下packetReader

 private Packet nextPacket() {        Packet packet = null;        // Wait until there's a packet or we're done.        while (!done && (packet = queue.poll()) == null) {            try {                synchronized (queue) {                    queue.wait();                }            }            catch (InterruptedException ie) {                // Do nothing            }        }        return packet;    }

三、PacketReader(接收消息)

同样这边的PacketReader构造方法中调用了初始化方法init,我们来看看,这里的主要两个方法parsePackets和resetParser,parsePackets则是为接收到的消息做解析的准备,暂时先放下,这里同样也是开启了一个线程 readerThread,resetParser则是初始化XML解析器和输入输出流parser.setInput(connection.reader),为我们parsePackets解析接收到的数据做铺垫,下面我们看下parsePackets方法,由于比较多,这里显示部分代码

 protected void init() {        done = false;        connectionID = null;        readerThread = new Thread() {            public void run() {                parsePackets(this);            }        };        readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")");        readerThread.setDaemon(true);        // Create an executor to deliver incoming packets to listeners. We'll use a single        // thread with an unbounded queue.        listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {            public Thread newThread(Runnable runnable) {                Thread thread = new Thread(runnable,                        "Smack Listener Processor (" + connection.connectionCounterValue + ")");                thread.setDaemon(true);                return thread;            }        });        resetParser();    }private void resetParser() {        try {            parser = XmlPullParserFactory.newInstance().newPullParser();            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);            parser.setInput(connection.reader);        }        catch (XmlPullParserException xppe) {            xppe.printStackTrace();        }    }

这里采用的pull解析xml数据,由于把这里的xml数据主要有三个节点,message、iq、presence,分别代表的是消息、请求响应、出席,我们先来看message节点的处理,这里调用的是PacketParserUtils.parseMessage,里面同样也是XmlPullParser解析器解析数据,拿到数据后将数据封装为Message对象后然后返回,获取id、to、from、type等数据,然后我们看看processPacket(packet)方法,具体做了哪些操作

 public static Packet parseMessage(XmlPullParser parser) throws Exception {        Message message = new Message();        String id = parser.getAttributeValue("", "id");        message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);        message.setTo(parser.getAttributeValue("", "to"));        message.setFrom(parser.getAttributeValue("", "from"));        message.setType(Message.Type.fromString(parser.getAttributeValue("", "type")));        String language = getLanguageAttribute(parser);         return message;  } private void parsePackets(Thread thread) {  try {            int eventType = parser.getEventType();            do {                if (eventType == XmlPullParser.START_TAG) {                    int parserDepth = parser.getDepth();                    ParsingExceptionCallback callback = connection.getParsingExceptionCallback();                    if (parser.getName().equals("message")) {                        Packet packet;                        try {                            packet = PacketParserUtils.parseMessage(parser);                        } catch (Exception e) {                            String content = PacketParserUtils.parseContentDepth(parser, parserDepth);                            UnparsablePacket message = new UnparsablePacket(content, e);                            if (callback != null) {                                callback.handleUnparsablePacket(message);                            }                            continue;                        }                        processPacket(packet);                    }                    else if (parser.getName().equals("iq")) {                        IQ iq;                        try {                            iq = PacketParserUtils.parseIQ(parser, connection);                        } catch (Exception e) {                            String content = PacketParserUtils.parseContentDepth(parser, parserDepth);                            UnparsablePacket message = new UnparsablePacket(content, e);                            if (callback != null) {                                callback.handleUnparsablePacket(message);                            }                            continue;                        }                        processPacket(iq);                    }                    else if (parser.getName().equals("presence")) {                        Presence presence;                        try {                            presence = PacketParserUtils.parsePresence(parser);                        } catch (Exception e) {                            String content = PacketParserUtils.parseContentDepth(parser, parserDepth);                            UnparsablePacket message = new UnparsablePacket(content, e);                            if (callback != null) {                                callback.handleUnparsablePacket(message);                            }                            continue;                        }                        processPacket(presence);                    } }

这里出现listenerExecutor,存储了监听的消息池,然后调用了ListenerNotification通知了所有的消息监听器接收消息,并且自己去判断消息的类型的来源,而 collector.processPacket(packet)则是将当前接收到的消息存储到消息队列中,发送和接收的消息处于同一消息队列

 private void processPacket(Packet packet) {        if (packet == null) {            return;        }        // Loop through all collectors and notify the appropriate ones.        for (PacketCollector collector: connection.getPacketCollectors()) {            collector.processPacket(packet);        }        // Deliver the incoming packet to listeners.        listenerExecutor.submit(new ListenerNotification(packet));    } private class ListenerNotification implements Runnable {        private Packet packet;        public ListenerNotification(Packet packet) {            this.packet = packet;        }        public void run() {            for (ListenerWrapper listenerWrapper : connection.recvListeners.values()) {                listenerWrapper.notifyListener(packet);            }        }    }  protected void processPacket(Packet packet) {        if (packet == null) {            return;        }        if (packetFilter == null || packetFilter.accept(packet)) {            while (!resultQueue.offer(packet)) {                // Since we know the queue is full, this poll should never actually block.                resultQueue.poll();            }        }    }