一、PacketReader實例化
PacketReader 在 XmppConnection中被實例化:
PacketReader packetReader = new PacketReader(this);
1、PacketReader 的構造方法
PacketReader實例化執行的構造方法如下:
protected PacketReader(final XMPPConnection connection) {
this.connection = connection; //connect成爲類的成員變量
this.init();
}
2、初始化
protected void init() {
done = false; //只有在connectError和shutdown的時候,纔有done = true; done是 parsePackets方法中條件循環的判斷條件之一,只有done = false 纔可以讀取socket
connectionID = null;
readerThread = new Thread() { //創建一個用於讀取socket輸入流的線程
public void run() {
parsePackets(this); // A、數據解析和裝換的入口,在readerThread線程啓動前,此方法沒有被執行。
}
};
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() { // B、這裏創建一個單線程的線程池,這個線程池是 packet 監聽機制的起始點。
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable,
"Smack Listener Processor (" + connection.connectionCounterValue + ")");
thread.setDaemon(true);
return thread;
}
});
resetParser(); //此方法創建了一個解析器MXParser的實例,在下面詳細分析
}
3、解析器初始化
此方法將connect類中的封裝socket 輸入流的reader 作爲解析器的輸入,既PacketReader中的解析器MXParser輸入 指向了socket 的inputStream 。
private void resetParser() {
try {
parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(connection.reader);
}
catch (XmlPullParserException xppe) {
xppe.printStackTrace();
}
}
resetParser()方法執行完後,PacketReader實例化完成,然後執行packetReader.startup()。
二、啓動readerThread線程
packetReader.startup()啓動了readerThread線程:
synchronized public void startup() throws XMPPException {
readerThread.start(); //指向 parsePackets(this); 方法(即上面一、2紅色的A標記處)
try {
int waitTime = SmackConfiguration.getPacketReplyTimeout();
wait(3 * waitTime);
}
catch (InterruptedException ie) {
}
if (connectionID == null) {
throw new XMPPException("Connection failed. No response from server.");
}
else {
connection.connectionID = connectionID;
}
}
三、解析數據
parsePackets(this)是數據解析、轉換和分發的入口。
private void parsePackets(Thread thread) {
try {
int eventType = parser.getEventType(); //從parser中獲取數據,判斷事件類型。
do {
if (eventType == XmlPullParser.START_TAG) {
int parserDepth = parser.getDepth();
ParsingExceptionCallback callback = connection.getParsingExceptionCallback();
if (parser.getName().equals("message")) { //處理 Message類型
Packet packet;
try {
packet = PacketParserUtils.parseMessage(parser); //從parser中獲取數據,生成message,完成xml到java對象的轉換
} catch (Exception e) {
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
UnparsedMessage message = new UnparsedMessage(content, e);
if (callback != null) {
callback.messageParsingException(e, message);
}
continue;
}
processPacket(packet); //分發packet,進入packetListener 的入口。
}
else if (parser.getName().equals("iq")) {
IQ iq;
try {
iq = PacketParserUtils.parseIQ(parser, connection); //從parser中獲取數據,生成iq,完成xml到java對象的轉換
} catch (Exception e) {
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
UnparsedIQ uniq = new UnparsedIQ(content, e);
if (callback != null) {
callback.iqParsingException(e, uniq);
}
continue;
}
processPacket(iq); //分發packet,進入packetListener 的入口。
}
else if (parser.getName().equals("presence")) {
Presence presence;
try {
presence = PacketParserUtils.parsePresence(parser); //從parser中獲取數據,生成presence,完成xml到java對象的轉換
} catch (Exception e) {
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
UnparsedPresence unpresence = new UnparsedPresence(content, e);
if (callback != null) {
callback.presenceParsingException(e, unpresence);
}
continue;
}
processPacket(presence); //分發packet,進入packetListener 的入口。
}
// We found an opening stream. Record information about it, then notify
// the connectionID lock so that the packet reader startup can finish.
else if (parser.getName().equals("stream")) {
// Ensure the correct jabber:client namespace is being used.
if ("jabber:client".equals(parser.getNamespace(null))) {
// Get the connection id.
for (int i=0; i<parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("id")) {
// Save the connectionID
connectionID = parser.getAttributeValue(i);
if (!"1.0".equals(parser.getAttributeValue("", "version"))) {
// Notify that a stream has been opened if the
// server is not XMPP 1.0 compliant otherwise make the
// notification after TLS has been negotiated or if TLS
// is not supported
releaseConnectionIDLock();
}
}
else if (parser.getAttributeName(i).equals("from")) {
// Use the server name that the server says that it is.
connection.config.setServiceName(parser.getAttributeValue(i));
}
}
}
}
else if (parser.getName().equals("error")) {
throw new XMPPException(PacketParserUtils.parseStreamError(parser));
}
else if (parser.getName().equals("features")) {
parseFeatures(parser);
}
else if (parser.getName().equals("proceed")) {
// Secure the connection by negotiating TLS
connection.proceedTLSReceived();
// Reset the state of the parser since a new stream element is going
// to be sent by the server
resetParser();
}
else if (parser.getName().equals("failure")) {
String namespace = parser.getNamespace(null);
if ("urn:ietf:params:xml:ns:xmpp-tls".equals(namespace)) {
// TLS negotiation has failed. The server will close the connection
throw new Exception("TLS negotiation has failed");
}
else if ("http://jabber.org/protocol/compress".equals(namespace)) {
// Stream compression has been denied. This is a recoverable
// situation. It is still possible to authenticate and
// use the connection but using an uncompressed connection
connection.streamCompressionDenied();
}
else {
// SASL authentication has failed. The server may close the connection
// depending on the number of retries
final Failure failure = PacketParserUtils.parseSASLFailure(parser);
processPacket(failure);
connection.getSASLAuthentication().authenticationFailed(failure.getCondition());
}
}
else if (parser.getName().equals("challenge")) {
// The server is challenging the SASL authentication made by the client
String challengeData = parser.nextText();
processPacket(new Challenge(challengeData));
connection.getSASLAuthentication().challengeReceived(challengeData);
}
else if (parser.getName().equals("success")) {
processPacket(new Success(parser.nextText()));
// We now need to bind a resource for the connection
// Open a new stream and wait for the response
connection.packetWriter.openStream();
// Reset the state of the parser since a new stream element is going
// to be sent by the server
resetParser();
// The SASL authentication with the server was successful. The next step
// will be to bind the resource
connection.getSASLAuthentication().authenticated();
}
else if (parser.getName().equals("compressed")) {
// Server confirmed that it's possible to use stream compression. Start
// stream compression
connection.startStreamCompression();
// Reset the state of the parser since a new stream element is going
// to be sent by the server
resetParser();
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("stream")) {
// Disconnect the connection
connection.disconnect();
}
}
eventType = parser.next();
} while (!done && eventType != XmlPullParser.END_DOCUMENT && thread == readerThread); //此處判斷done 等條件,開始進入循環體
}
catch (Exception e) {
// 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())) {
// Close the connection and notify connection listeners of the
// error.
connection.notifyConnectionError(e);
}
}
}
五、處理數據
上面第四步中,所有的 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)); //在這裏packet 進入線程池,並由ListenerNotification開啓監聽模式。
}
總結:
readerThread 線程啓動後,以 parser 爲數據源,從socket inputStream中讀取數據並解析,完成從xml數據到java對象的轉換過程。
XML數據可以分爲很多種類型,目前比較關注的是 Message 、Iq、Presence 。
補充:
第三步,parsePacket()方法中,會根據 parser.getName() 得到的類型調用不同的解析方法,這些方法針對 Message,IQ和Presence是不同的。
parsePackets(Thread thread) 用到了
PacketParserUtils 類,
PacketParserUtils有一點特殊的地方。
在解析Message、IQ 和 Presence的時候,用到了
ProviderManager.getInstance() 方法。
在connect.connect()執行成功後,在解析 IQ、Message、Presence之前,要設置
ProviderManager.getInstance().addIQProvider("notification", "androidpn:iq:notification", newNotificationIQProvider());
或者
ProviderManager.getInstance().addExtensionProvider("myExtension", "androidpn:iq:myextension", new MyExtensionProvider());
下一步 從ListenerNotification做爲入口分析。