在研究如何實現Pushing功能期間,收集了很多關於Pushing的資料,其中有一個androidnp開源項目用的人比較多,但是由於長時間沒有什么人去維護,聽說bug的幾率挺多的,為了以后自己的產品穩定些,所以就打算自己研究一下asmack的源碼,自己做一個插件,androidnp移動端的源碼中包含了一個叫做asmack的jar。
Reader和Writer
在asmack中有兩個非常重要的對象PacketReader和PacketWriter,那么從類名上看Packet + (Reader/Wirter),而TCP/IP傳輸的數據,叫做Packet(包),asmack使用的是XMPP協議,XMPP簡單講就是使用TCP/IP協議 + XML流協議的組合。所以這個了對象的作用從字面上看應該是,寫包與讀包,作用為從服務端讀寫數據。
PacketWriter中一定含有一個Writer對象,這個Writer是一個輸出流,同樣的PacketReader對象中有一個Reader,而這個Reader是一個輸入流,Writer和Reader對象就是一個簡單的讀寫器,他們是從socket對象中獲取出來后,經過裝飾變成現在這個樣子。
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
沒有什么神奇的地方,主要看PacketWriter/Reader,這兩個對象分別把對應的Writer和Reader引用到自己的內部進行操作,下面就先看一個PacketWriter。
1 /** 2 * Creates a new packet writer with the specified connection. 3 * 4 * @param connection the connection. 5 */ 6 protected PacketWriter(XMPPConnection connection) { 7 this.queue = new ArrayBlockingQueue<Packet>(500, true); 8 this.connection = connection; 9 init(); 10 }
還有就是PacketWriter初始化的時候將XMPPConnection對象傳了進來,因為在init方法中使用到了XMPPConnection對象的writer成員,我想說的是,為什么不直接傳遞writer成員?而是將整個對象XMPPConnection傳了過來?其實這就是設計模式的好處,我們如果每次都傳遞的是自己的成員,那么如果后期有改動,實現一個新的XMPPConnection與PacketWriter關聯,那么老的代碼維護起來是很巨大的,如果這里XMPPConnection和他的同事類PacketWriter都有相對應的接口,(XMPPConnection的接口是Connection)那就更完美了,而這里用到的模式應該是中介者,不是絕對意義的中介者,由於形成中介者的條件比較高,所以實際開發中多是變形使用。PacketWriter對象在XMPPConnection中的connect方法中被初始化,它的最大作用是在其自身的內部創建了兩個消息循環,其中一個用30s的heartbeats向服務器發送空白字符,保持長連接。而第二個循環則時刻從隊列中主動取消息並發往服務器,而向外部提供的sendPacket方法則是向queue中添加消息,前面提到的循環機制都是在線程中工作,而消息的隊列用的是ArrayBlockingQueue,這個無邊界阻塞隊列可以存放任何對象,這里存放的是Packet對象。
1 public void sendPacket(Packet packet) { 2 if (!done) { 3 try { 4 queue.put(packet); 5 } 6 catch (InterruptedException ie) { 7 ie.printStackTrace(); 8 return; 9 } 10 synchronized (queue) { 11 queue.notifyAll(); 12 } 13 } 14 }
1 while (!done && (writerThread == thisThread)) { 2 Packet packet = nextPacket(); 3 if (packet != null) { 4 synchronized (writer) { 5 writer.write(packet.toXML()); 6 writer.flush(); 7 // Keep track of the last time a stanza was sent to the server 8 lastActive = System.currentTimeMillis(); 9 } 10 } 11 }
消息循環則是一個通過各種成員變量控制的while loop,第一行的nextPacket方法是向queue中獲取Packet消息,並且通過weiter將包發出去,這樣生產/消費的模型就搭建好了,這里需要注意的是,我刪減了很多影響閱讀的代碼,並沒有全部貼上。關於heartbeats循環其實也是一個在線程中運行的while loop,也是通過一些成員控制。wirter向服務端寫了寫什么?看下面的這個方法
1 void openStream() throws IOException { 2 StringBuilder stream = new StringBuilder(); 3 stream.append("<stream:stream"); 4 stream.append(" to=\"").append(connection.getServiceName()).append("\""); 5 stream.append(" xmlns=\"jabber:client\""); 6 stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); 7 stream.append(" version=\"1.0\">"); 8 writer.write(stream.toString()); 9 writer.flush(); 10 }
XML,沒錯,這也是符合XMPP協議規范的一種表現吧,至於更多XMPP協議的好處,由於本人的經驗有限,就不多做點評,希望后續會對其深入了解。
下面看一個PacketReader這個類都包含了什么職責。