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()方法終於告一個段落了。