基于XMPP协议的aSmack源码分析【2】PacketReader
PacketReader
PacketReader所有的核心逻辑都在一个线程中完成的,PacketReader的工作很专注,同样的在一个while loop中 不停的解析、刷新reader对象、同时作为事件源发送解析过后的各种Packet,解析这里用的是Android独特的Pull解析,Pull解析的特点事件驱动,在这里被完全的利用了起来,随着不同的标签,PacketReader都会做出不同的处理,处理完这些数据用不同Pocket对象封装,最后,分发出去,由监听者做最后的业务处理。
1 readerThread = new Thread() { 2 public void run() { 3 parsePackets(this); 4 } 5 };
由于解析过程的代码量过于多,我写到什么地方就分解什么地方,大家有时间最好自己看源码。
一、初始化/重置解析器
1 private void resetParser() { 2 try { 3 //用的是Pull解析 4 parser = XmlPullParserFactory.newInstance().newPullParser(); 5 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 6 parser.setInput(connection.reader); 7 } 8 catch (XmlPullParserException xppe) { 9 xppe.printStackTrace(); 10 } 11 }
上面这个resetParser方法还会在解析的过程中碰到不同的业务需求会不断的被调用,有用和业务逻辑比较紧密,没什么技术含量,关键是要看解析的方式和同时作为事件源发送解析过后的各种Packet,这两部分的设计,是非常的迷人的。
二、解析
1 do { 2 if (eventType == XmlPullParser.START_TAG) { 3 if (parser.getName().equals("message")) { 4 processPacket(PacketParserUtils.parseMessage(parser)); 5 } 6 else if (parser.getName().equals("iq")) { 7 processPacket(PacketParserUtils.parseIQ(parser, connection)); 8 } 9 else if (parser.getName().equals("presence")) { 10 processPacket(PacketParserUtils.parsePresence(parser)); 11 }
PacketParserUtils是一个工具类,各个静态方法传入的还是Parser对象,内部同样的使用Pull的方式进行解析,但是由于Pull是驱动解析,不会无故的浪费资源只会加载感兴趣的内容,试想一下,如果这里用Dom解析……PacketParserUtils的这些静态解析方法返回的实例对象也不一样,从方法名可以看出有IQ、message、presence等,他们的父类为Packet,这些对象又被执行processPacket方法的时候传入
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)); }
processPacket方法内部有一个循环来转调collector.processPacket(packet);方法,前提是connection.getPacketCollectors()内部有货,到目前位置都没有涉及到PacketCollector这个接口的内容,他的作用其实是一个观察者模式中的执行者的作用,也就是传说中的监听器,凡是注册了它的对象,都可以通过processPacket这个抽象方法,监听packet的变化。可是到现在任何对象都没有注册它,所以这个Loop还没有作用,因为目前我们还处在连接的步骤(还没绕出来)。
1 listenerExecutor.submit(new ListenerNotification(packet));其中ListenerNotification是个Runnable 2 /** 3 * A runnable to notify all listeners of a packet. 4 */ 5 private class ListenerNotification implements Runnable { 6 7 private Packet packet; 8 9 public ListenerNotification(Packet packet) { 10 this.packet = packet; 11 } 12 13 public void run() { 14 for (ListenerWrapper listenerWrapper : connection.recvListeners.values()) { 15 listenerWrapper.notifyListener(packet); 16 } 17 } 18 }
我们上面看到listenerExecutor是一个线程池,在线程池中执行了一个凡是注册了ListenerWrapper的对象,都将接收到packet,同样的,到目前为止没有对象注册,(在RegisterTask过程中ListenerWrapper被注册)
else if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals("stream")) { // Disconnect the connection connection.disconnect(); } }
当文档读取结束是将断开连接
void cleanup() { connection.recvListeners.clear(); connection.collectors.clear(); }
看到了吗,只是将监听器接口集合清空而已,并没有断开连接,或者取消消息循环
PacketReader对象的startup方法比较复杂,大体上执行了读取流,并将解析好的Packet对象发送给观察者,由观察者继续后续操作,目前观察者还没有出现,还有就是使用了线程池和令牌来操作执行线程,而且维护了一个connectionID成员,这个成员的作用还需要再看,这就不多说了。
关于Packet对象,packet对象有很多子类,上面举例了3个,其实还有很多,都是在parser时封装的
AuthMechanism\Challenge\Failure\IQ\Message\Presence\Response\Success
还有就是Pull解析的优点体现了出来,可以一个parser对象包含了很多信息,但可能没到一个时刻我们需要的信息只是一小部分,这样用Pull解析的驱动式就大大减少了冗余的过程,PacketReader对象使用了2个监听器集合对象,PacketCollector、listenerWrapper,还是那句话,还没看到观察者,所以还不知道什么情况下需要注册这两个监听。
到目前位置packetReader.startup()方法终于告一个段落了。