一、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出去