MQTT源碼解析

 

一、MQTT的創建和connect流程

1.Android端實現mqtt,首先會new一個MqttAndroidClient,並傳入需要的參數。

首先,MqttAndroidClient是如何創建的呢?

public MqttAndroidClient(Context context, String serverURI,
			String clientId, MqttClientPersistence persistence, Ack ackType) {
		myContext = context;
		this.serverURI = serverURI;
		this.clientId = clientId;
		this.persistence = persistence;
		messageAck = ackType;
	}

MqttAndroidClient本身是繼承Broadcast的子類,他的構造器需要傳入上面的參數,必須傳入的爲

public MqttAndroidClient(Context context, String serverURI,
			String clientId) {
		this(context, serverURI, clientId, null, Ack.AUTO_ACK);
	}

2.connect的核心方法:

@Override
	public IMqttToken connect(MqttConnectOptions options, Object userContext,
			IMqttActionListener callback) throws MqttException {

		IMqttToken token = new MqttTokenAndroid(this, userContext,
				callback);

		connectOptions = options;
		connectToken = token;

		/*
		 * The actual connection depends on the service, which we start and bind
		 * to here, but which we can't actually use until the serviceConnection
		 * onServiceConnected() method has run (asynchronously), so the
		 * connection itself takes place in the onServiceConnected() method
		 */
		if (mqttService == null) { // First time - must bind to the service
			Intent serviceStartIntent = new Intent();
			serviceStartIntent.setClassName(myContext, SERVICE_NAME);
			Object service = myContext.startService(serviceStartIntent);
			if (service == null) {
				IMqttActionListener listener = token.getActionCallback();
				if (listener != null) {
					listener.onFailure(token, new RuntimeException(
							"cannot start service " + SERVICE_NAME));
				}
			}

			// We bind with BIND_SERVICE_FLAG (0), leaving us the manage the lifecycle
			// until the last time it is stopped by a call to stopService()
			myContext.bindService(serviceStartIntent, serviceConnection,
					Context.BIND_AUTO_CREATE);

			if (!receiverRegistered) registerReceiver(this);
		}
		else {
			pool.execute(new Runnable() {

				@Override
				public void run() {
					doConnect();
					
					//Register receiver to show shoulder tap.
					if (!receiverRegistered) registerReceiver(MqttAndroidClient.this);
				}

			});
		}

		return token;
	}

返回一個IMqttToken對象,這裏首先new一個MqttTokenAndroid對象,然後去判斷是否存在MqttService實例。

沒有的話會創建一個,如果創建失敗直接調用IMqttActionListener的onFailure回調。

成功的話綁定一個bindService,傳入ServiceConnection參數對象。

而ServiceConnection的成功回調中有個doConnect()方法,裏面會調用MqttService的connect方法從而建立Mqtt的連接。

而在MqttService的connect方法裏面,會創建一個MqttConnection對象,調用他的connect方法。

而MqttConnection的connect方法裏面會判斷是否有MqttAsyncClient實例,如果沒有會創建他的實例並調用他的connect方法

// if myClient is null, then create a new connection
			else {
				alarmPingSender = new AlarmPingSender(service);
				myClient = new MqttAsyncClient(serverURI, clientId,
						persistence, alarmPingSender);
				myClient.setCallback(this);

				service.traceDebug(TAG,"Do Real connect!");
				setConnectingState(true);
				myClient.connect(connectOptions, invocationContext, listener);

而在MqttAsyncClient的connect方法裏面,會調用ConnectActionListener的connect方法;然後調用了ClientCommms的connect方法。

/**
	 * Sends a connect message and waits for an ACK or NACK.
	 * Connecting is a special case which will also start up the
	 * network connection, receive thread, and keep alive thread.
	 * @param options The {@link MqttConnectOptions} for the connection
	 * @param token The {@link MqttToken} to track the connection
	 * @throws MqttException if an error occurs when connecting
	 */
	public void connect(MqttConnectOptions options, MqttToken token) throws MqttException {
		final String methodName = "connect";
		synchronized (conLock) {
			if (isDisconnected() && !closePending) {
				//@TRACE 214=state=CONNECTING
				log.fine(CLASS_NAME,methodName,"214");

				conState = CONNECTING;

				conOptions = options;

                MqttConnect connect = new MqttConnect(client.getClientId(),
                        conOptions.getMqttVersion(),
                        conOptions.isCleanSession(),
                        conOptions.getKeepAliveInterval(),
                        conOptions.getUserName(),
                        conOptions.getPassword(),
                        conOptions.getWillMessage(),
                        conOptions.getWillDestination());

                this.clientState.setKeepAliveSecs(conOptions.getKeepAliveInterval());
                this.clientState.setCleanSession(conOptions.isCleanSession());
                this.clientState.setMaxInflight(conOptions.getMaxInflight());

				tokenStore.open();
				ConnectBG conbg = new ConnectBG(this, token, connect, executorService);
				conbg.start();
			}
			else {
				// @TRACE 207=connect failed: not disconnected {0}
				log.fine(CLASS_NAME,methodName,"207", new Object[] {new Byte(conState)});
				if (isClosed() || closePending) {
					throw new MqttException(MqttException.REASON_CODE_CLIENT_CLOSED);
				} else if (isConnecting()) {
					throw new MqttException(MqttException.REASON_CODE_CONNECT_IN_PROGRESS);
				} else if (isDisconnecting()) {
					throw new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
				} else {
					throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED);
				}
			}
		}
	}

而ClientComms的connect會調用內部類,名爲ConnectBG的一個Thread線程子類,後面就是按照Mqtt的報文格式進行發送和接收message的流程了。

而啓動的線程屬於之前在MqttAsyncAndroid裏創建的線程池的線程,受其管理。

發送用一個CommsSender的線程管理,接收用一個CommsReceiver的線程管理,鎖用一個conLock對象來管理,來確保同時只能進行單一操作。

Mqtt的報文格式放在MqttWireMessage裏面。

簡單看待的話:Mqtt其實就是實現了一種規定協議的通訊方式,通過一個叫MqttService的服務來維護,用Thread線程池來管理通訊。而他的功能只要包括:connect連接,publish訂閱,topic主題和disconnect斷開連接和close關閉。

我們普通開發者調用的話主要是依賴MqttAndroidClient這個類裏面的方法,而Mqtt實現的主要方法是MqttAsyncClient來控制整個流程。

需要注意的一點是:

MqttAndroidClient是一個廣播,在connect的時候會註冊,但是停止使用的時候理論上需要調用MqttAndroidClient的方法:

/**
	 * Unregister receiver which receives intent from MqttService avoids
	 * IntentReceiver leaks.
	 */
	public void unregisterResources(){
		if(myContext != null && receiverRegistered){
			synchronized (MqttAndroidClient.this) {
				LocalBroadcastManager.getInstance(myContext).unregisterReceiver(this);
				receiverRegistered = false;
			}
			if(bindedService){
				try{
					myContext.unbindService(serviceConnection);
					bindedService = false;
				}catch(IllegalArgumentException e){
					//Ignore unbind issue.
				}
			}
		}
	}

來進行解除註冊,防止內存泄漏問題。

但是你如果直接使用的是MqttClient的話,就不需要考慮這個問題。但是MqttClient的話並沒有MqttService的支持,所以你需要考慮後臺保活的話,使用MqttAndroidClient就不需要考慮這個問題,Android端的封裝會使用起來更加簡單。

 

二、MqttCallbackExtended的回調

當設置Mqtt自動斷線重連的時候,需要調用MqttCallbackExtended回調接口,因爲該回調接口當回調成功的時候,回調一個方法connectComplete,而該方法的調用位置是在ConnectActionListener的onSuccess()方法中。

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

而自動重連實現的方法爲:

在MqttAsyncClient類中有個內部類:MqttReconnectCallback,並實現了MqttCallbackextended接口

class MqttReconnectCallback implements MqttCallbackExtended {

		final boolean automaticReconnect;

		MqttReconnectCallback(boolean isAutomaticReconnect) {
			automaticReconnect = isAutomaticReconnect;
		}

		public void connectionLost(Throwable cause) {
			if (automaticReconnect) {
				// Automatic reconnect is set so make sure comms is in resting
				// state
				comms.setRestingState(true);
				reconnecting = true;
				startReconnectCycle();
			}
		}

		public void messageArrived(String topic, MqttMessage message) throws Exception {
		}

		public void deliveryComplete(IMqttDeliveryToken token) {
		}

		public void connectComplete(boolean reconnect, String serverURI) {
		}

	}

當斷開連接之後,會調用connectionLost方法的startreconnectCycle()方法

	private void startReconnectCycle() {
		String methodName = "startReconnectCycle";
		// @Trace 503=Start reconnect timer for client: {0}, delay: {1}
		log.fine(CLASS_NAME, methodName, "503", new Object[] { this.clientId, new Long(reconnectDelay) });
		reconnectTimer = new Timer("MQTT Reconnect: " + clientId);
		reconnectTimer.schedule(new ReconnectTask(), reconnectDelay);
	}

會啓動一個內部類ReconnectTask

	private class ReconnectTask extends TimerTask {
		private static final String methodName = "ReconnectTask.run";

		public void run() {
			// @Trace 506=Triggering Automatic Reconnect attempt.
			log.fine(CLASS_NAME, methodName, "506");
			attemptReconnect();
		}
	}
	private void attemptReconnect() {
		final String methodName = "attemptReconnect";
		// @Trace 500=Attempting to reconnect client: {0}
		log.fine(CLASS_NAME, methodName, "500", new Object[] { this.clientId });
		try {
			connect(this.connOpts, this.userContext, new MqttReconnectActionListener(methodName));
		} catch (MqttSecurityException ex) {
			// @TRACE 804=exception
			log.fine(CLASS_NAME, methodName, "804", null, ex);
		} catch (MqttException ex) {
			// @TRACE 804=exception
			log.fine(CLASS_NAME, methodName, "804", null, ex);
		}
	}

會調用connect方法嘗試連接

會跳到MqttConnection的connect方法,然後會判斷是不是斷線重連

if(connectOptions.isAutomaticReconnect()){
			//The Automatic reconnect functionality is enabled here
			Log.i(TAG, "Requesting Automatic reconnect using New Java AC");
			final Bundle resultBundle = new Bundle();
			resultBundle.putString(
					MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
					reconnectActivityToken);
			resultBundle.putString(
					MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null);
			resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
					MqttServiceConstants.CONNECT_ACTION);
			try {
				myClient.reconnect();
			} catch (MqttException ex){
				Log.e(TAG, "Exception occurred attempting to reconnect: " + ex.getMessage());
				setConnectingState(false);
				handleException(resultBundle, ex);
			}
		} 

斷線重連會執行reconnect方法,

	public void reconnect() throws MqttException {
		final String methodName = "reconnect";
		// @Trace 500=Attempting to reconnect client: {0}
		log.fine(CLASS_NAME, methodName, "500", new Object[] { this.clientId });
		// Some checks to make sure that we're not attempting to reconnect an
		// already connected client
		if (comms.isConnected()) {
			throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED);
		}
		if (comms.isConnecting()) {
			throw new MqttException(MqttException.REASON_CODE_CONNECT_IN_PROGRESS);
		}
		if (comms.isDisconnecting()) {
			throw new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
		}
		if (comms.isClosed()) {
			throw new MqttException(MqttException.REASON_CODE_CLIENT_CLOSED);
		}
		// We don't want to spam the server
		stopReconnectCycle();

		attemptReconnect();
	}

週而復始~直到連接成功

這裏強調一點:每次連接失敗之後,延遲執行的時間會更改

每次會等待之前時間的2倍之後纔再次請求,當時間大於128s之後,每隔128秒請求一次

if (reconnectDelay < 128000) {
				reconnectDelay = reconnectDelay * 2;
			}
			rescheduleReconnectCycle(reconnectDelay);

三、訂閱subscribe

首先執行MqttAndroidClient的subscribe方法,調用MqttService的subscribe方法

public IMqttToken subscribe(String topic, int qos, Object userContext,
			IMqttActionListener callback) throws MqttException {
		IMqttToken token = new MqttTokenAndroid(this, userContext,
				callback, new String[]{topic});
		String activityToken = storeToken(token);
		mqttService.subscribe(clientHandle, topic, qos, null, activityToken);
		return token;
	}

MqttService中啓動一個MqttConnection對象,調用它的subscribe方法

如果沒有connect的話,會創建一個Bundle對象,把參數保存進去,啓動一個廣播發送出去。

void callbackToActivity(String clientHandle, Status status,
      Bundle dataBundle) {
    // Don't call traceDebug, as it will try to callbackToActivity leading
    // to recursion.
    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);
  }

這個廣播是之前說的那個動態廣播MqttAndroidClient。

然後在onReceive方法裏面,根據

MqttServiceConstants.CALLBACK_ACTION

來獲取對應的action

這個action的值在之前Bundle裏面存放

MqttServiceConstants.SUBSCRIBE_ACTION

 

如果connect連接的話,就調用MqttAsyncClient的subscribe方法

public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback)
			throws MqttException {
		final String methodName = "subscribe";

		if (topicFilters.length != qos.length) {
			throw new IllegalArgumentException();
		}

		// remove any message handlers for individual topics
		for (int i = 0; i < topicFilters.length; ++i) {
			this.comms.removeMessageListener(topicFilters[i]);
		}

		// Only Generate Log string if we are logging at FINE level
		if (log.isLoggable(Logger.FINE)) {
			StringBuffer subs = new StringBuffer();
			for (int i = 0; i < topicFilters.length; i++) {
				if (i > 0) {
					subs.append(", ");
				}
				subs.append("topic=").append(topicFilters[i]).append(" qos=").append(qos[i]);

				// Check if the topic filter is valid before subscribing
				MqttTopic.validate(topicFilters[i], true/* allow wildcards */);
			}
			// @TRACE 106=Subscribe topicFilter={0} userContext={1} callback={2}
			log.fine(CLASS_NAME, methodName, "106", new Object[] { subs.toString(), userContext, callback });
		}

		MqttToken token = new MqttToken(getClientId());
		token.setActionCallback(callback);
		token.setUserContext(userContext);
		token.internalTok.setTopics(topicFilters);

		MqttSubscribe register = new MqttSubscribe(topicFilters, qos);

		comms.sendNoWait(register, token);
		// @TRACE 109=<
		log.fine(CLASS_NAME, methodName, "109");

		return token;
	}

創建一個MqttSubscribe對象組包,然後調用MqttCommons的sendNoWait發送

其實sendNoWait做的就是用MqttState的send方法,把數據給一個

Vector類型的對象,相當於存到發送的隊列裏面,然後sender出去
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章