Android MQTT消息推送實踐

這裏寫圖片描述

大概的消息推送過程就是這樣,手機端訂閱(Subscribe)一個Topic,當服務器有新消息的時候就發佈(public)到所有的訂閱者哪裏去.

MQTT全稱Message Queuing Telemetry Transport,消息隊列遙測傳輸

本文記錄Android客戶端的實現.實現參考自開源項目https://www.eclipse.org/paho/

在開始閱讀Android客戶端代碼之前,先看一下Java SE平臺中如何使用MQTT.jar

以下代碼給出瞭如何發佈消息到服務器:

import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;


/**
 *@Description:
 *@author lx
 *@date 2017-1-12 下午1:19:42
 */
public class TestMQTT {

    public static void main(String args[]){

        //消息的類型
          String topic        = "TOPIC MQTT Examples";
          //消息內容
          String content      = "XX發佈了消息";
          //消息發送的模式   選擇消息發送的次數,依據不同的使用環境使用不同的模式
          int qos             = 2;
          //服務器地址
          String broker       = "tcp://iot.eclipse.org:1883";
          //客戶端的唯一標識
          String clientId     = "CLIENTID JavaSample";
          //消息緩存的方式  內存緩存
          MemoryPersistence persistence = new MemoryPersistence();

          try {
              //創建以惡搞MQTT客戶端
              MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
              //消息的配置參數
              MqttConnectOptions connOpts = new MqttConnectOptions();
              //不記憶上一次會話
              connOpts.setCleanSession(true);
              System.out.println("Connecting to broker: "+broker);
              //鏈接服務器
              sampleClient.connect(connOpts);
              System.out.println("Connected");
              System.out.println("Publishing message: "+content);
              //創建消息
              MqttMessage message = new MqttMessage(content.getBytes());
              //給消息設置發送的模式
              message.setQos(qos);
              //發佈消息到服務器
              sampleClient.publish(topic, message);
              System.out.println("Message published");
              //斷開鏈接
              sampleClient.disconnect();
              System.out.println("Disconnected");
              System.exit(0);
          } catch(MqttException me) {
              System.out.println("reason "+me.getReasonCode());
              System.out.println("msg "+me.getMessage());
              System.out.println("loc "+me.getLocalizedMessage());
              System.out.println("cause "+me.getCause());
              System.out.println("excep "+me);
              me.printStackTrace();
          }

    }

}

使用上邊的代碼可以發佈消息到服務器。

客戶端接受消息:Demo示例參考https://github.com/eclipse/paho.mqtt.android

在該Demo中給出瞭如何運行該Demo的方法。Demo中包含3個Module。
org.eclipse.paho.android.service
客戶端的MQTT需要運行在一個服務中,這個Module對MQTT的操作進行封裝。

org.eclipse.paho.android.sample
這個Module演示瞭如何使用上邊給出的Service。該Service既有訂閱也有發佈功能。一般我們使用訂閱即可。

paho.mqtt.android.example

沒有使用Service,也沒有斷線重連機制。代碼量非常少,但是演示可訂閱的核心操作。建議先看這一個。

下面主要分析org.eclipse.paho.android.sample

首次運行需要,畫面如下:

這裏寫圖片描述

由於沒有沒有連接到服務器,所以這裏需要創建一個鏈接。

這裏寫圖片描述

這裏寫圖片描述

點擊保存,一個鏈接信息保存完畢。在實際開發過程中,我們的與服務器的鏈接信息是直接在代碼中寫死的。

到這裏我們將鏈接的信息已經保存起來了。下一次打開的時候就會顯示如下畫面

這裏寫圖片描述

打開Toolbar上的開關,會調用MQTT的Connect方法,建立與服務器的鏈接。

// org.eclipse.paho.android.sample.activity.ConnectionFragment

connectSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
((MainActivity) getActivity()).connect(connection);
changeConnectedState(true);
} else {
((MainActivity) getActivity()).disconnect(connection);
changeConnectedState(false);
}
}
});

可見這裏當打開開關的時候,建立連接。進入connect方法

// org.eclipse.paho.android.sample.activity.MainActivity

public void connect(Connection connection) {
String[] actionArgs = new String[1];
actionArgs[0] = connection.getId();
final ActionListener callback = new ActionListener(this,
ActionListener.Action.CONNECT, connection, actionArgs);
connection.getClient().setCallback(new MqttCallbackHandler(this, connection.handle()));
try {
//這裏重點 connection.getClient().connect(connection.getConnectionOptions(), null, callback);
}
catch (MqttException e) {
Log.e(this.getClass().getCanonicalName(),
“MqttException Occured”, e);
}
}

這裏又調用connection.getClient().connect

Connection代表我們之前保存的服務器信息,當讓,我麼可以根據Connection中保存的信息來創建一個Client對象。所以這裏調用getClient方法,其實內部是一個創建對象的過程。這個Client對象並不是我們實際上的MQTT.jar中的Client,而是一個廣播接收者。

public MqttAndroidClient getClient() {
return client;
}
而MqttAndroidClient

public class MqttAndroidClient extends BroadcastReceiver implements
IMqttAsyncClient

這個廣播接受者的connect方法中又啓動了一個綁定的服務。在這個服務中才真正運行MQTT.jar.我們後續所有的操作都是通過這個Service來對MQTT.jar進行操作的。當MQTT.jar操作完成,會發送廣播,這樣我們就可以收到消息訂閱成功的狀態了。
/*
* 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);

//當然,其實廣播也不是MQTT發送,不過可以這樣簡單的理解。

廣播都是我們自己定義的。MQTT怎麼知道發什麼樣的廣播呢?所以,在訂閱消息的時候,我們會給MQTT設置一個對調函數,當調用成功後就會產生一個回掉,這時我們自己在這裏發送廣播通知消息接受成功。

訂閱消息的代碼由從Service開始分析,前面的調用就掠過了。

public void subscribe(final String topic, final int qos,
String invocationContext, String activityToken) {
service.traceDebug(TAG, “subscribe({” + topic + “},” + qos + “,{”
+ invocationContext + “}, {” + activityToken + “}”);
final Bundle resultBundle = new Bundle();
resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
MqttServiceConstants.SUBSCRIBE_ACTION);
resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
activityToken);
resultBundle.putString(
MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
invocationContext);

if ((myClient != null) && (myClient.isConnected())) {
IMqttActionListener listener = new MqttConnectionListener(
resultBundle);
try {
//看這裏
myClient.subscribe(topic, qos, invocationContext, listener);
} catch (Exception e) {
handleException(resultBundle, e);
}
} else {
resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
NOT_CONNECTED);
service.traceError(“subscribe”, NOT_CONNECTED);
service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
}
}
Service裏調用的myClient.subscribe(topic, qos, invocationContext, listener);
這裏的myClient就是MQTT裏的Client對象的。看見沒有,這裏給設置了一個listener.就是這個回掉函數。

我們看看這個回掉函數的實現MqttConnectionListener,這裏類創建接受一個Boundle對象。Boundle裏邊設置了
resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
MqttServiceConstants.SUBSCRIBE_ACTION);

CALLBACK——ACTION

對就是這個常量。

然後在這個實現類的onSuccess方法中傳入Service的就是方纔的Boundle
@Override
public void onSuccess(IMqttToken asyncActionToken) {
service.callbackToActivity(clientHandle, Status.OK, resultBundle);

然後在看你Service的callbackToActivity

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);
}

}

傳入的Boundle由被放到一個Intent中。最後以廣播的方式發送出去。
if (dataBundle != null) {
callbackIntent.putExtras(dataBundle);
}

這時,我們在MqttAndroidClient
這個廣播接收者中就收到了訂閱成功的消息。

關於消息接受:

消息是怎樣接受到的,我們沒有去監聽消息,MQTT幫我們做了。同樣是回掉。

在創建一個MqttConnection中鏈接的時候竄入一個this
myClient = new MqttAsyncClient(serverURI, clientId,
persistence, new AlarmPingSender(service));
myClient.setCallback(this);

看Callback這方法的參數
public void setCallback(MqttCallback callback) {
this.mqttCallback = callback;
comms.setCallback(callback);
}

public interface MqttCallback {
/**
* This method is called when the connection to the server is lost.
*
* @param cause the reason behind the loss of connection.
*/
public void connectionLost(Throwable cause);

/**
* This method is called when a message arrives from the server.
*
*


* This method is invoked synchronously by the MQTT client. An
* acknowledgment is not sent back to the server until this
* method returns cleanly.


*


* If an implementation of this method throws an Exception, then the
* client will be shut down. When the client is next re-connected, any QoS
* 1 or 2 messages will be redelivered by the server.


*


* Any additional messages which arrive while an
* implementation of this method is running, will build up in memory, and
* will then back up on the network.


*


* If an application needs to persist data, then it
* should ensure the data is persisted prior to returning from this method, as
* after returning from this method, the message is considered to have been
* delivered, and will not be reproducible.


*


* It is possible to send a new message within an implementation of this callback
* (for example, a response to this message), but the implementation must not
* disconnect the client, as it will be impossible to send an acknowledgment for
* the message being processed, and a deadlock will occur.


*
* @param topic name of the topic on the message was published to
* @param message the actual message.
* @throws Exception if a terminal error has occurred, and the client should be
* shut down.
*/
public void messageArrived(String topic, MqttMessage message) throws Exception;

/**
* Called when delivery for a message has been completed, and all
* acknowledgments have been received. For QoS 0 messages it is
* called once the message has been handed to the network for
* delivery. For QoS 1 it is called when PUBACK is received and
* for QoS 2 when PUBCOMP is received. The token will be the same
* token as that returned when the message was published.
*
* @param token the delivery token associated with the message.
*/
public void deliveryComplete(IMqttDeliveryToken token);

}
可以看到,messageArrived

Ok,這裏消息也接受到了,接受到繼續發廣播。

有點亂,湊合看看,以後再整理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章