物聯網協議之MQTT原始碼分析(二)

若丶相見發表於2019-05-12

此篇文章繼上一篇物聯網協議之MQTT原始碼分析(一)而寫的第二篇MQTT釋出訊息以及接收Broker訊息的原始碼分析,想看MQTT連線的小夥伴可以去看我上一篇哦。

juejin.im/post/5cd66c…

MQTT釋出訊息

MQTT釋出訊息是由MqttAndroidClient類的publish函式執行的,我們來看看這個函式:

// MqttAndroidClient類:
    @Override
    public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
                                      boolean retained, Object userContext,
                                      IMqttActionListener callback)
            throws MqttException, MqttPersistenceException {
        // 將訊息內容、qos訊息等級、retained訊息是否保留封裝成MqttMessage
        MqttMessage message = new MqttMessage(payload);
        message.setQos(qos);
        message.setRetained(retained);
        // 每一條訊息都有自己的token
        MqttDeliveryTokenAndroid token = new MqttDeliveryTokenAndroid(
                this, userContext, callback, message);
        String activityToken = storeToken(token);
        IMqttDeliveryToken internalToken = mqttService.publish(clientHandle,
                topic, payload, qos, retained, null, activityToken);
        token.setDelegate(internalToken);
        return token;
    }
複製程式碼

從上面程式碼可以看出,釋出訊息需要topic訊息主題、payload訊息內容、callback回撥監聽等,經由mqttService.publish繼續執行釋出操作:

// MqttService類:MQTT唯一元件
    public IMqttDeliveryToken publish(String clientHandle, String topic,
                                      byte[] payload, int qos, boolean retained,
                                      String invocationContext, String activityToken)
            throws MqttPersistenceException, MqttException {
        MqttConnection client = getConnection(clientHandle);
        return client.publish(topic, payload, qos, retained, invocationContext,
                activityToken);
    }
複製程式碼

MqttConnection在上一篇中講解過,MQTT的連線會初始化一個MqttConnection,並儲存在一個Map集合connections中,並通過getConnection(clientHandle)方法獲取。很明顯我們要接著看client.publish函式啦:

// MqttConnection類:
    public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
                                      boolean retained, String invocationContext,
									  String activityToken) {
		// 用於釋出訊息,是否釋出成功的回撥
        final Bundle resultBundle = new Bundle();
        resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
                MqttServiceConstants.SEND_ACTION);
        resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
                activityToken);
        resultBundle.putString(
                MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
                invocationContext);

        IMqttDeliveryToken sendToken = null;

        if ((myClient != null) && (myClient.isConnected())) {
            // 攜帶resultBundle資料,用於監聽回撥發布訊息是否成功
            IMqttActionListener listener = new MqttConnectionListener(
                    resultBundle);
            try {
                MqttMessage message = new MqttMessage(payload);
                message.setQos(qos);
                message.setRetained(retained);
                sendToken = myClient.publish(topic, payload, qos, retained,
                        invocationContext, listener);
                storeSendDetails(topic, message, sendToken, invocationContext,
                        activityToken);
            } catch (Exception e) {
                handleException(resultBundle, e);
            }
        } else {
            resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
                    NOT_CONNECTED);
            service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED);
            service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
        }

        return sendToken;
    }
複製程式碼

這段程式碼中很明顯可以看出釋出的操作又交給了myClient.publish方法,那myClient是誰呢?上一篇文章中講過myClient是MqttAsyncClient,是在MQTT連線時在MqttConnection類的connect方法中初始化的,詳情請看上一篇。

// MqttAsyncClient類:
    public IMqttDeliveryToken publish(String topic, byte[] payload, int qos
        , boolean retained,Object userContext,
        IMqttActionListener callback) throws MqttException,MqttPersistenceException {
        MqttMessage message = new MqttMessage(payload);
        message.setQos(qos);
        message.setRetained(retained);
        return this.publish(topic, message, userContext, callback);
    }
    
    public IMqttDeliveryToken publish(String topic, MqttMessage message
        , Object userContext,
        IMqttActionListener callback) throws MqttException,MqttPersistenceException {
        final String methodName = "publish";
        // @TRACE 111=< topic={0} message={1}userContext={1} callback={2}
        log.fine(CLASS_NAME, methodName, "111", new Object[]{topic, userContext, callback});

        // Checks if a topic is valid when publishing a message.
        MqttTopic.validate(topic, false/* wildcards NOT allowed */);

        MqttDeliveryToken token = new MqttDeliveryToken(getClientId());
        token.setActionCallback(callback);
        token.setUserContext(userContext);
        token.setMessage(message);
        token.internalTok.setTopics(new String[]{topic});

        MqttPublish pubMsg = new MqttPublish(topic, message);
        comms.sendNoWait(pubMsg, token);

        // @TRACE 112=<
        log.fine(CLASS_NAME, methodName, "112");

        return token;
    }
複製程式碼

從這段程式碼中可以看到,現在把把topic和message封裝成了MqttPublish型別的訊息,並繼續由comms.sendNoWait執行,comms是ClientComms,ClientComms是在初始化MqttAsyncClient的構造方法中初始化的,詳情看上一篇。

// ClientComms類:
    public void sendNoWait(MqttWireMessage message, MqttToken token) throws MqttException {
        final String methodName = "sendNoWait";
        // 判斷狀態或者訊息型別
        if (isConnected() ||
                (!isConnected() && message instanceof MqttConnect) ||
                (isDisconnecting() && message instanceof MqttDisconnect)) {
            if (disconnectedMessageBuffer != null && disconnectedMessageBuffer.getMessageCount() != 0) {
                //@TRACE 507=Client Connected, Offline Buffer available, but not empty. Adding 
                // message to buffer. message={0}
                log.fine(CLASS_NAME, methodName, "507", new Object[]{message.getKey()});
                if (disconnectedMessageBuffer.isPersistBuffer()) {
                    this.clientState.persistBufferedMessage(message);
                }
                disconnectedMessageBuffer.putMessage(message, token);
            } else {
                // 現在不是disconnect因此,邏輯走這裡
                this.internalSend(message, token);
            }
        } else if (disconnectedMessageBuffer != null) {
            //@TRACE 508=Offline Buffer available. Adding message to buffer. message={0}
            log.fine(CLASS_NAME, methodName, "508", new Object[]{message.getKey()});
            if (disconnectedMessageBuffer.isPersistBuffer()) {
                this.clientState.persistBufferedMessage(message);
            }
            disconnectedMessageBuffer.putMessage(message, token);
        } else {
            //@TRACE 208=failed: not connected
            log.fine(CLASS_NAME, methodName, "208");
            throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
        }
    }
    
    void internalSend(MqttWireMessage message, MqttToken token) throws MqttException {
        final String methodName = "internalSend";
        ...
        try {
            // Persist if needed and send the message
            this.clientState.send(message, token);
        } catch (MqttException e) {
            // 注意此處程式碼***
            if (message instanceof MqttPublish) {
                this.clientState.undo((MqttPublish) message);
            }
            throw e;
        }
    }
複製程式碼

comms.sendNoWait方法中又呼叫了本類中的internalSend方法,並且在internalSend方法中又呼叫了clientState.send(message, token)方法繼續釋出。ClientState物件是在ClientComms初始化的構造方法中初始化的。此處需要注意一下catch裡的程式碼,下面會具體說明。

// ClientState類:
    public void send(MqttWireMessage message, MqttToken token) throws MqttException {
        final String methodName = "send";
        ...

        if (message instanceof MqttPublish) {
            synchronized (queueLock) {
                /**
                 * 注意這裡:actualInFlight實際飛行中>maxInflight最大飛行中
                 * maxInflight:是我們在自己程式碼中通過連線選項MqttConnectOptions.setMaxInflight();設定的,預設大小為10
                */
                if (actualInFlight >= this.maxInflight) {
                    //@TRACE 613= sending {0} msgs at max inflight window
                    log.fine(CLASS_NAME, methodName, "613",
                            new Object[]{new Integer(actualInFlight)});

                    throw new MqttException(MqttException.REASON_CODE_MAX_INFLIGHT);
                }

                MqttMessage innerMessage = ((MqttPublish) message).getMessage();
                //@TRACE 628=pending publish key={0} qos={1} message={2}
                log.fine(CLASS_NAME, methodName, "628",
                        new Object[]{new Integer(message.getMessageId()),
                                new Integer(innerMessage.getQos()), message});
                /**
                 * 根據自己設定的qos等級,來決定是否需要恢復訊息
                 * 這裡需要說明一下qos等級區別:
                 *  qos==0,至多傳送一次,不進行重試,Broker不會返回確認訊息。
                 *  qos==1,至少傳送一次,確保訊息到達Broker,Broker需要返回確認訊息PUBACK
                 *  qos==2,Broker肯定會收到訊息,且只收到一次,qos==1可能會傳送重複訊息
                */
                switch (innerMessage.getQos()) {
                    case 2:
                        outboundQoS2.put(new Integer(message.getMessageId()), message);
                        persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
                        break;
                    case 1:
                        outboundQoS1.put(new Integer(message.getMessageId()), message);
                        persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
                        break;
                }
                tokenStore.saveToken(token, message);
                pendingMessages.addElement(message);
                queueLock.notifyAll();
            }
        } else {
            ...
        }
    }
複製程式碼

這段程式碼中我們發現了一個可能需要我們自己設定的屬性maxInflight,如果實際傳送中的訊息大於maxInflight約束的最大的話就會丟擲MqttException異常,那麼這個異常catch裡是怎麼處理的呢,這就要往回看一步程式碼啦,上面已經提示過需要注意ClientComms類中internalSend方法中的catch裡的程式碼:

    if (message instanceof MqttPublish) {
        this.clientState.undo((MqttPublish) message);
    }
複製程式碼

可以很明確的看出若訊息型別是MqttPublish,則執行clientState.undo((MqttPublish) message)方法,我們前面說過訊息已經在MqttAsyncClient類的publish方法中把topic和message封裝成了MqttPublish型別的訊息,因此此處會執行undo方法:

// ClientState類:
    protected void undo(MqttPublish message) throws MqttPersistenceException {
        final String methodName = "undo";
        synchronized (queueLock) {
            //@TRACE 618=key={0} QoS={1} 
            log.fine(CLASS_NAME, methodName, "618",
                    new Object[]{new Integer(message.getMessageId()),
                            new Integer(message.getMessage().getQos())});

            if (message.getMessage().getQos() == 1) {
                outboundQoS1.remove(new Integer(message.getMessageId()));
            } else {
                outboundQoS2.remove(new Integer(message.getMessageId()));
            }
            pendingMessages.removeElement(message);
            persistence.remove(getSendPersistenceKey(message));
            tokenStore.removeToken(message);
            if (message.getMessage().getQos() > 0) {
                //Free this message Id so it can be used again
                releaseMessageId(message.getMessageId());
                message.setMessageId(0);
            }

            checkQuiesceLock();
        }
    }
複製程式碼

程式碼已經很明顯了,就是把大於maxInflight這部分訊息remove移除掉,因此在實際操作中要注意自己的Mqtt訊息的釋出會不會在短時間內達到maxInflight預設的10的峰值,若能達到,則需要手動設定一個適合自己專案的範圍閥值啦。

我們繼續說clientState.send(message, token)方法裡的邏輯,程式碼中註釋中也說明了Mqtt會根據qos等級來決定訊息到達機制

qos等級

  • qos==0,至多傳送一次,不進行重試,Broker不會返回確認訊息,訊息可能會丟失。
  • qos==1,至少傳送一次,確保訊息到達Broker,Broker需要返回確認訊息PUBACK,可能會傳送重複訊息
  • qos==2,Broker肯定會收到訊息,且只收到一次

根據qos等級,若qos等於1和2,則講訊息分別加入Hashtable型別的outboundQoS1和outboundQoS2中,已在後續邏輯中確保訊息傳送成功併到達。

注:qos等級優先順序沒有maxInflight高,從程式碼中可以看出,會先判斷maxInflight再區分qos等級

程式碼的最後講訊息新增進Vector型別的pendingMessages裡,在上一篇中我們可以瞭解到MQTT的發射器是輪詢檢查pendingMessages裡是否存在資料,若存在則通過socket的OutputStream傳送出去。並且會通過接收器接收從Broker傳送回來的資料。

監聽Broker返回的訊息之資料

傳送我們就不看原始碼啦,接收我們再看一下原始碼,通過原始碼看一看資料是怎麼回到我們自己的回撥裡的:

// CommsReceiver類中:
    public void run() {
        recThread = Thread.currentThread();
        recThread.setName(threadName);
        final String methodName = "run";
        MqttToken token = null;

        try {
            runningSemaphore.acquire();
        } catch (InterruptedException e) {
            running = false;
            return;
        }

        while (running && (in != null)) {
            try {
                //@TRACE 852=network read message
                log.fine(CLASS_NAME, methodName, "852");
                receiving = in.available() > 0;
                MqttWireMessage message = in.readMqttWireMessage();
                receiving = false;

                // 訊息是否屬於Mqtt確認型別
                if (message instanceof MqttAck) {
                    token = tokenStore.getToken(message);
                    // token一般不會為空,前面已經儲存過
                    if (token != null) {
                        synchronized (token) {
                            // ...
                            clientState.notifyReceivedAck((MqttAck) message);
                        }
                    } 
                    ...
            } finally {
                receiving = false;
                runningSemaphore.release();
            }
        }
    }
複製程式碼

從程式碼中可以看出,Broker返回來的資料交給了clientState.notifyReceivedAck方法:

// ClientState類:
    protected void notifyReceivedAck(MqttAck ack) throws MqttException {
        final String methodName = "notifyReceivedAck";
        ...

        MqttToken token = tokenStore.getToken(ack);
        MqttException mex = null;

        if (token == null) {
            ...
        } else if (ack instanceof MqttPubRec) {
            // qos==2 是返回
            MqttPubRel rel = new MqttPubRel((MqttPubRec) ack);
            this.send(rel, token);
        } else if (ack instanceof MqttPubAck || ack instanceof MqttPubComp) {
            // qos==1/2 訊息移除前通知的結果
            notifyResult(ack, token, mex);
            // Do not remove publish / delivery token at this stage
            // do this when the persistence is removed later 
        } else if (ack instanceof MqttPingResp) {
            // 連線心跳資料訊息
            ...
        } else if (ack instanceof MqttConnack) {
            // MQTT連線訊息
            ...
        } else {
            notifyResult(ack, token, mex);
            releaseMessageId(ack.getMessageId());
            tokenStore.removeToken(ack);
        }

        checkQuiesceLock();
    }
複製程式碼

從上面註釋可知,釋出的訊息qos==0,返回結果是直接走else,而qos==1/2,確認訊息也最終會走到notifyResult(ack, token, mex)方法中:

    protected void notifyResult(MqttWireMessage ack, MqttToken token, MqttException ex) {
        final String methodName = "notifyResult";
        // 取消阻止等待令牌的任何執行緒,並儲存ack
        token.internalTok.markComplete(ack, ex);
        // 通知此令牌已收到響應訊息,設定已完成狀態,並通過isComplete()獲取狀態
        token.internalTok.notifyComplete();

        // 讓使用者知道非同步操作已完成,然後刪除令牌
        if (ack != null && ack instanceof MqttAck && !(ack instanceof MqttPubRec)) {
            //@TRACE 648=key{0}, msg={1}, excep={2}
            log.fine(CLASS_NAME, methodName, "648", new Object[]{token.internalTok.getKey(), ack,ex});
            // CommsCallback類
            callback.asyncOperationComplete(token);
        }
        // 有些情況下,由於操作失敗,因此沒有確認
        if (ack == null) {
            //@TRACE 649=key={0},excep={1}
            log.fine(CLASS_NAME, methodName, "649", new Object[]{token.internalTok.getKey(), ex});
            callback.asyncOperationComplete(token);
        }
    }
    
// Token類:
    protected void markComplete(MqttWireMessage msg, MqttException ex) {
        final String methodName = "markComplete";
        //@TRACE 404=>key={0} response={1} excep={2}
        log.fine(CLASS_NAME, methodName, "404", new Object[]{getKey(), msg, ex});

        synchronized (responseLock) {
            // ACK means that everything was OK, so mark the message for garbage collection.
            if (msg instanceof MqttAck) {
                this.message = null;
            }
            this.pendingComplete = true;
            // 將訊息儲存在response成員變數中,並通過getWireMessage()方法獲取訊息msg
            this.response = msg;
            this.exception = ex;
        }
    }
// Token類:
    protected void notifyComplete() {
        ...
        synchronized (responseLock) {
            ...
            if (exception == null && pendingComplete) {
                // 設定已完成,並通過isComplete()獲取狀態
                completed = true;
                pendingComplete = false;
            } else {
                pendingComplete = false;
            }

            responseLock.notifyAll();
        }
        ...
    }
複製程式碼

此時已將MqttWireMessage訊息儲存到token中,非同步操作已完成,呼叫回撥監聽CommsCallback裡的asyncOperationComplete方法:

// CommsCallback類:
    public void asyncOperationComplete(MqttToken token) {
        final String methodName = "asyncOperationComplete";

        if (running) {
            // invoke callbacks on callback thread
            completeQueue.addElement(token);
            synchronized (workAvailable) {
                // @TRACE 715=new workAvailable. key={0}
                log.fine(CLASS_NAME, methodName, "715", new Object[]{token.internalTok.getKey()});
                workAvailable.notifyAll();
            }
        } else {
            // invoke async callback on invokers thread
            try {
                handleActionComplete(token);
            } catch (Throwable ex) {
                // Users code could throw an Error or Exception e.g. in the case
                // of class NoClassDefFoundError
                // @TRACE 719=callback threw ex:
                log.fine(CLASS_NAME, methodName, "719", null, ex);

                // Shutdown likely already in progress but no harm to confirm
                clientComms.shutdownConnection(null, new MqttException(ex));
            }
        }
    }
複製程式碼

CommsCallback是Mqtt連線就已經開始一直執行,因此running為true,所以現在已經將token新增進了completeQueue完成佇列中,CommsCallback跟發射器一樣,一直輪詢等待資料,因此此時completeQueue已有資料,此時CommsCallback的run函式則會有接下來的操作:

// CommsCallback類:
    public void run() {
        ...
        while (running) {
            try {
                ...
                if (running) {
                    // Check for deliveryComplete callbacks...
                    MqttToken token = null;
                    synchronized (completeQueue) {
                        // completeQueue不為空
                        if (!completeQueue.isEmpty()) {
                            // 獲取第一個token
                            token = (MqttToken) completeQueue.elementAt(0);
                            completeQueue.removeElementAt(0);
                        }
                    }
                    if (null != token) {
                        // token不為null,執行handleActionComplete
                        handleActionComplete(token);
                    }
                    ...
                }

                if (quiescing) {
                    clientState.checkQuiesceLock();
                }

            } catch (Throwable ex) {
                ...
            } finally {
                ...
            }
        }
    }
    
    private void handleActionComplete(MqttToken token)
            throws MqttException {
        final String methodName = "handleActionComplete";
        synchronized (token) {
            // 由上面已經,isComplete()已設定為true
            if (token.isComplete()) {
                // Finish by doing any post processing such as delete 
                // from persistent store but only do so if the action
                // is complete
                clientState.notifyComplete(token);
            }
            // 取消阻止任何服務員,如果待完成,現在設定完成
            token.internalTok.notifyComplete();
            if (!token.internalTok.isNotified()) {
 				...
				// 現在呼叫非同步操作完成回撥
				fireActionEvent(token);
			}
			...
        }
    }
複製程式碼

run中呼叫了handleActionComplete函式,接著後呼叫了clientState.notifyComplete()方法和fireActionEvent(token)方法,先看notifyComplete():

// ClientState類:
   protected void notifyComplete(MqttToken token) throws MqttException {

       final String methodName = "notifyComplete";
       // 獲取儲存到Token中的Broker返回的訊息,上面有說明
       MqttWireMessage message = token.internalTok.getWireMessage();

       if (message != null && message instanceof MqttAck) {
           ...
           MqttAck ack = (MqttAck) message;

           if (ack instanceof MqttPubAck) {
               // qos==1,使用者通知現在從永續性中刪除
               persistence.remove(getSendPersistenceKey(message));
               persistence.remove(getSendBufferedPersistenceKey(message));
               outboundQoS1.remove(new Integer(ack.getMessageId()));
               decrementInFlight();
               releaseMessageId(message.getMessageId());
               tokenStore.removeToken(message);
               // @TRACE 650=removed Qos 1 publish. key={0}
               log.fine(CLASS_NAME, methodName, "650",
                       new Object[]{new Integer(ack.getMessageId())});
           } else if (ack instanceof MqttPubComp) {
               ...
           }

           checkQuiesceLock();
       }
   }
複製程式碼

再來看fireActionEvent(token)方法:

// CommsCallback類:
   public void fireActionEvent(MqttToken token) {
       final String methodName = "fireActionEvent";

       if (token != null) {
           IMqttActionListener asyncCB = token.getActionCallback();
           if (asyncCB != null) {
               if (token.getException() == null) {
                   ...
                   asyncCB.onSuccess(token);
               } else {
                   ...
                   asyncCB.onFailure(token, token.getException());
               }
           }
       }
   }
複製程式碼

從這段程式碼中終於能看到回撥onSuccess和onFailure的方法啦,那asyncCB是誰呢?

// MqttToken類:
   public IMqttActionListener getActionCallback() {
       return internalTok.getActionCallback();
   }
// Token類:
   public IMqttActionListener getActionCallback() {
   	return callback;
   }
複製程式碼

看到這,一臉懵逼,這到底是誰呢,其實我們可以直接看這個回撥設定方法,看看是從哪設定進來的就可以啦:

// Token類:
   public void setActionCallback(IMqttActionListener listener) {
   	this.callback  = listener;
   }
// MqttToken類:
   public void setActionCallback(IMqttActionListener listener) {
   	internalTok.setActionCallback(listener);
   }
// ConnectActionListener類:
   public void connect() throws MqttPersistenceException {
       // 初始化MqttToken
       MqttToken token = new MqttToken(client.getClientId());
       // 將此類設定成回撥類
       token.setActionCallback(this);
       token.setUserContext(this);

       ...
   }
複製程式碼

其實早在MQTT連線時,就已經將此callback設定好,因此asyncCB就是ConnectActionListener,所以此時就已經走到了ConnectActionListener類裡的onSuccess和onFailure的方法中,我們只挑一個onSuccess看一看:

// ConnectActionListener類:
   public void onSuccess(IMqttToken token) {
       if (originalMqttVersion == MqttConnectOptions.MQTT_VERSION_DEFAULT) {
           options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_DEFAULT);
       }
       // 此時將Broker的資料儲存進了userToken裡
       userToken.internalTok.markComplete(token.getResponse(), null);
       userToken.internalTok.notifyComplete();
       userToken.internalTok.setClient(this.client); 

       comms.notifyConnect();

       if (userCallback != null) {
           userToken.setUserContext(userContext);
           userCallback.onSuccess(userToken);
       }

       if (mqttCallbackExtended != null) {
           String serverURI =
                   comms.getNetworkModules()[comms.getNetworkModuleIndex()].getServerURI();
           mqttCallbackExtended.connectComplete(reconnect, serverURI);
       }

   }
複製程式碼

這裡的userCallback又是誰呢?上一篇其實說過的,userCallback其實就是MqttConnection.connect函式中IMqttActionListener listener,所以此時又來到了MqttConnection類裡connect方法裡的listener監聽回撥內:

// MqttConnection類:
   public void connect(MqttConnectOptions options, String invocationContext,
                       String activityToken) {
       ...
       service.traceDebug(TAG, "Connecting {" + serverURI + "} as {" + clientId + "}");
       final Bundle resultBundle = new Bundle();
       resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
               activityToken);
       resultBundle.putString(
               MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
               invocationContext);
       resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
               MqttServiceConstants.CONNECT_ACTION);
       try {
            ...
           // 此時邏輯已經來到這裡
           IMqttActionListener listener = new MqttConnectionListener(
                   resultBundle) {

               @Override
               public void onSuccess(IMqttToken asyncActionToken) {
                   // 執行如下程式碼:
                   doAfterConnectSuccess(resultBundle);
                   service.traceDebug(TAG, "connect success!");
               }

               @Override
               public void onFailure(IMqttToken asyncActionToken,
                                     Throwable exception) {
                   resultBundle.putString(
                           MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
                           exception.getLocalizedMessage());
                   resultBundle.putSerializable(
                           MqttServiceConstants.CALLBACK_EXCEPTION, exception);
                   service.traceError(TAG,
                           "connect fail, call connect to reconnect.reason:"
                                   + exception.getMessage());

                   doAfterConnectFail(resultBundle);

               }
           };

           if (myClient != null) {
               if (isConnecting) {
                   ...
               } else {
                   service.traceDebug(TAG, "myClient != null and the client is not connected");
                   service.traceDebug(TAG, "Do Real connect!");
                   setConnectingState(true);
                   myClient.connect(connectOptions, invocationContext, listener);
               }
           }

           // if myClient is null, then create a new connection
           else {
               ...
               myClient.connect(connectOptions, invocationContext, listener);
           }
       } catch (Exception e) {
           ...
       }
   }
複製程式碼

由這段程式碼以及註釋可以知道,現在以及執行到了MqttConnection類裡的doAfterConnectSuccess方法裡:

// MqttConnection類:
   private void doAfterConnectSuccess(final Bundle resultBundle) {
       // 獲取喚醒鎖
       acquireWakeLock();
       service.callbackToActivity(clientHandle, Status.OK, resultBundle);
       deliverBacklog();
       setConnectingState(false);
       disconnected = false;
       // 釋放喚醒鎖
       releaseWakeLock();
   }
   
   private void deliverBacklog() {
       Iterator<StoredMessage> backlog = service.messageStore
               .getAllArrivedMessages(clientHandle);
       while (backlog.hasNext()) {
           StoredMessage msgArrived = backlog.next();
           Bundle resultBundle = messageToBundle(msgArrived.getMessageId(),
                   msgArrived.getTopic(), msgArrived.getMessage());
           // 關注下這個action,下面會用到
           resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
                   MqttServiceConstants.MESSAGE_ARRIVED_ACTION);
           service.callbackToActivity(clientHandle, Status.OK, resultBundle);
       }
   }
複製程式碼

可以看到這個函式中呼叫了幾個方法中的其中兩個service.callbackToActivity(clientHandle, Status.OK, resultBundle);和deliverBacklog();,deliverBacklog()方法最後也是呼叫的service.callbackToActivity方法。所以直接看service.callbackToActivity:

// MqttService類:
   void callbackToActivity(String clientHandle, Status status,
                           Bundle dataBundle) {
       // 傳送廣播
       Intent callbackIntent = new Intent(
               MqttServiceConstants.CALLBACK_TO_ACTIVITY);
       if (clientHandle != null) {
           callbackIntent.putExtra(
                   MqttServiceConstants.CALLBACK_CLIENT_HANDLE, clientHandle);
       }
       callbackIntent.putExtra(MqttServiceConstants.CALLBACK_STATUS, status);
       if (dataBundle != null) {
           callbackIntent.putExtras(dataBundle);
       }
       LocalBroadcastManager.getInstance(this).sendBroadcast(callbackIntent);
   }
複製程式碼

service.callbackToActivity方法其實就是傳送廣播,那誰來接收廣播呢?其實接收廣播的就在最開始的MqttAndroidClient,MqttAndroidClient繼承自BroadcastReceiver,所以說MqttAndroidClient本身就是一個廣播接收者,所以我們來看它的onReceive方法:

// MqttAndroidClient類:
   @Override
   public void onReceive(Context context, Intent intent) {
       Bundle data = intent.getExtras();

       String handleFromIntent = data
               .getString(MqttServiceConstants.CALLBACK_CLIENT_HANDLE);

       if ((handleFromIntent == null)
               || (!handleFromIntent.equals(clientHandle))) {
           return;
       }

       String action = data.getString(MqttServiceConstants.CALLBACK_ACTION);
       // 判斷訊息的action型別
       if (MqttServiceConstants.CONNECT_ACTION.equals(action)) {
           connectAction(data);
       } else if (MqttServiceConstants.CONNECT_EXTENDED_ACTION.equals(action)) {
           connectExtendedAction(data);
       } else if (MqttServiceConstants.MESSAGE_ARRIVED_ACTION.equals(action)) {
           messageArrivedAction(data);
       } else if (MqttServiceConstants.SUBSCRIBE_ACTION.equals(action)) {
           subscribeAction(data);
       } else if (MqttServiceConstants.UNSUBSCRIBE_ACTION.equals(action)) {
           unSubscribeAction(data);
       } else if (MqttServiceConstants.SEND_ACTION.equals(action)) {
           // 釋出成功與否的回撥
           sendAction(data);
       } else if (MqttServiceConstants.MESSAGE_DELIVERED_ACTION.equals(action)) {
           messageDeliveredAction(data);
       } else if (MqttServiceConstants.ON_CONNECTION_LOST_ACTION
               .equals(action)) {
           connectionLostAction(data);
       } else if (MqttServiceConstants.DISCONNECT_ACTION.equals(action)) {
           disconnected(data);
       } else if (MqttServiceConstants.TRACE_ACTION.equals(action)) {
           traceAction(data);
       } else {
           mqttService.traceError(MqttService.TAG, "Callback action doesn't exist.");
       }
   }
複製程式碼

從程式碼和註釋以及上面的deliverBacklog方法中可以知道,我們現在需要關注的action為MESSAGE_ARRIVED_ACTION,所以就可以呼叫方法messageArrivedAction(data):

// MqttAndroidClient類:
   private void messageArrivedAction(Bundle data) {
       if (callback != null) {
           String messageId = data
                   .getString(MqttServiceConstants.CALLBACK_MESSAGE_ID);
           String destinationName = data
                   .getString(MqttServiceConstants.CALLBACK_DESTINATION_NAME);

           ParcelableMqttMessage message = data
                   .getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL);
           try {
               if (messageAck == Ack.AUTO_ACK) {
                   callback.messageArrived(destinationName, message);
                   mqttService.acknowledgeMessageArrival(clientHandle, messageId);
               } else {
                   message.messageId = messageId;
                   callback.messageArrived(destinationName, message);
               }

               // let the service discard the saved message details
           } catch (Exception e) {
               // Swallow the exception
           }
       }
   }
   
   @Override
   public void setCallback(MqttCallback callback) {
       this.callback = callback;
   }
複製程式碼

在messageArrivedAction方法中可以看到,我們最後呼叫了callback回撥了messageArrived方法,那麼 callback通過上面下部分程式碼可以知道,其實這個callback就是我們上一篇文章中所說的我們初始化MqttAndroidClient後,通過方法setCallback設定的我們自己定義的實現MqttCallback介面的回撥類。

監聽Broker返回的訊息之釋出訊息成功與否

再看下sendAction(data)方法:

   private void sendAction(Bundle data) {
       IMqttToken token = getMqttToken(data); 
       // remove on delivery
       simpleAction(token, data);
   }
   
   private void simpleAction(IMqttToken token, Bundle data) {
       if (token != null) {
           Status status = (Status) data
                   .getSerializable(MqttServiceConstants.CALLBACK_STATUS);
           if (status == Status.OK) {
               // 如果釋出成功回撥此方法
               ((MqttTokenAndroid) token).notifyComplete();
           } else {
               Exception exceptionThrown =
                       (Exception) data.getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION);
               // 釋出失敗回撥
               ((MqttTokenAndroid) token).notifyFailure(exceptionThrown);
           }
       } else {
           if (mqttService != null) {
               mqttService.traceError(MqttService.TAG, "simpleAction : token is null");
           }
       }
   }
複製程式碼

接下來再看一看釋出成功回撥的MqttTokenAndroid的notifyComplete函式:

// MqttTokenAndroid類:
   void notifyComplete() {
       synchronized (waitObject) {
           isComplete = true;
           waitObject.notifyAll();
           if (listener != null) {
               listener.onSuccess(this);
           }
       }
   }
複製程式碼

這裡又呼叫了listener.onSuccess(this)方法,那麼這個listener是誰?其實listener就是我們呼叫MqttAndroidClient類的publish釋出的最後一個引數,即我們自定義的監聽釋出訊息是否釋出成功的回撥類。上面在MqttConnection類的publish方法中封裝過MqttServiceConstants.SEND_ACTION的Bundle資料,而此資料是被MqttConnection類裡的MqttConnectionListener攜帶。所以MqttConnectionListener裡的onSuccess被呼叫時就會呼叫service.callbackToActivity,繼而到sendBroadcast傳送廣播,最後呼叫sendAction方法,回撥自定義的IMqttActionListener的實現類。而MqttConnectionListener裡的onSuccess是在CommsCallback類裡的fireActionEvent方法中,往上走就到CommsCallback類的了handleActionComplete和run()函式。

現在看是不是有點懵畢竟上面有兩個 監聽Broker返回的訊息,一個是用來監聽Broker發給客戶端資料的監聽,另一個是客戶端釋出訊息是否釋出成功的監聽而已。兩者都是使用MqttActionListener,不過前者在MqttActionListener監聽回撥裡最後呼叫的是自定義的MqttCallback回撥而已。並且兩者監聽的位置不一樣,前者是在 MqttConnection類的connect時就已確認下來的,對於一個MQTT連線只會有一個,所以這個是一直用來監聽資料的;而後者監聽釋出訊息是否成功是每個publish都需要傳入的,並在MqttConnection類裡的publish初始化。這麼講是不是就清晰一些啦。

哈哈,到此MQTT的publish釋出以及接收Broker資料的原始碼分析也看完啦。

(注:若有什麼地方闡述有誤,敬請指正。)

相關文章