關鍵詞
JMS、ActiveMQ(ActivityMQ)、Apollo、MQTT、Android
摘要
由于項目開發需要,涉及到 Android 客戶端接收來自 JMS 中間件的消息推送,本文以學習過程為線索,進行記錄。
目錄
一、簡述 JMS (引出 ActiveMQ、Apollo)
二、MQTT 協議
三、MQTT 服務器搭建
四、 MQTT Android 客戶端具體實現
—— 1、添加依賴
—— 2、添加權限
—— 3、基本字段說明
—— 4、注冊Service
—— 5、Android 端具體實現
正文
一、簡述 JMS (引出 ActiveQM、Apollo)
企業消息系統(Java Message Service)
又稱之為面向消息的中間件(MOM),提供了應用程序之間,異步數據的存儲和轉發,即應用程序彼此不直接通信,而是與作為中介的 MOM 通信。應用程序開發人員無需了解消息的發送和協議的細節。
過程如下圖,應用程序 A 只需要將 Message 發送到服務器上,然后應用程序 B 即可從服務器中接收到 A 發來的消息,由此可知 JMS 具有提供消息靈活性,松散耦合等優點。
- JMS 由 SUN 提出,是一系列的接口及相關語義的集合,通過這些接口和和其中的方法,JMS 客戶端可以訪問消息系統,完成消息的創建、發送、接收及讀取。
- JMS 通過 MOM 為 Java 程序提供了一個發送和接收消息的標準。根據 JMS 編寫的客戶端程序可以訪問任何實現 JMS 標準的 MOM。
JMS兩種消息模型:
發送消息的客戶端:生產者, 接收消息的客戶端:消費者, MOM :消息傳遞系統
點到點(P2P)消息傳遞模型
- 生產者發送消息到MOM的一個特定隊列(Queue)中,而消費者從一個消息隊列中得到消息。
- 每條消息只有一個消費者,如果一條消息被消息者接收,那么其他的消費者就不能得到這條消息了。
- 收到消息后消費者必須確認消息已被接收,否則消息傳遞系統會認為該消息沒有被接收,那么這條消息仍然可以被其他人接收(程序可以自動進行確認,不需要人工干預)。
發布/訂閱(Pub/Sub)消息傳遞模型
- 發布/訂閱傳遞消息類型與主題(Topic)有關。生產者發布消息,而消費者訂閱感興趣的消息,生產者將消息和一個特定的主題(Topic)連在一起,消息傳遞系統根據消費者注冊的興趣,將消息傳遞給消費者。這種類型非常類似出版報紙、雜志的形式。
- 每個消息都可以有多個(0,1,……)訂閱者。即每條消息可以有多個消費者,如果報紙和雜志一樣。
- 訂閱者只能消費他們訂閱之后出版的消息。這就要求訂閱者必須先訂閱,再等待生產者的運行,發布。
- 訂閱者必須保持為活動狀態才能獲得這些消息,即訂閱者必須保持活動狀態等待發布者發布的消息。
關于 JMS 更詳細的介紹,推薦閱讀下面的貼,更加深入淺出,通俗易懂
深入淺出JMS(一)——JMS簡介
深入淺出JMS(二)——JMS的組成
ActiveMQ (或ActivityMQ)
由 Apache 貢獻的 ActiveMQ 正是 MOM 中優秀的一員。它是一個非常流行、強大、開源的消息和集成模式服務器,速度快、支持多種跨語言客戶端和協議,易于使用企業集成模式,擁有許多先進的特性,完全支持 JMS 1.1和 J2EE 1.4 規范。
Apollo
是以 ActiveMQ5.x 為基礎,采用全新的線程和消息調度架構重新實現的 MOM,針對多核處理器進行了優化處理,它的速度更快、更可靠、更易于維護。Apollo 與 ActiveMQ 一樣支持多協議:STOMP、AMQP、MQTT、Openwire、 SSL、WebSockets,本文只介紹 MQTT 協議的 Android 客戶端使用。
二、MQTT 協議
1、 Android 端實現消息推送的幾種方式
- 輪詢:客戶端定時向服務器請求數據,屬于偽推送。缺點:費電,費流量。
- XMPP:是基于可擴展標記語言(XML)的協議。缺點:XML 格式的,冗余很大,花費流量。
- MQTT:由 IBM 開發的傳輸協議,它被設計用于輕量級的發布/訂閱式消息傳輸,旨在為低帶寬和不穩定的網絡環境中的設備提供可靠的網絡服務。相比于 XMPP 等傳統協議,MQTT 是專門針對移動互聯網開發的輕量級傳輸協議,這種傳輸協議連接穩定、心跳數據包小,所以具備耗電量低、耗流量低的優勢。推送服務的最佳協議!
2、 MQTT 協議簡介
MQTT官網:http://mqtt.org/
MQTT介紹:http://www.ibm.com
MQTT Android github:https://github.com/eclipse/paho.mqtt.android
MQTT API:http://www.eclipse.org/paho/files/javadoc/index.html
MQTT Android API: http://www.eclipse.org/paho/files/android-javadoc/index.html
3、 Apollo + MQTT 協議的消息推送特征
- Apollo 使用 MQTT 協議,加上 Android 上的 paho 包,即可簡單實現消息通知功能,但是 MQTT 協議僅支持主題消息廣播(Topic),即發布/訂閱消息傳遞模式,而且不能用Selector,不能直接實現點對點的消息投遞。
- 利用上述的特征,可將 Android 客戶端劃分為眾多的部落,創建不同的主題, 針對特定訂閱者群體進行消息傳遞。該特征特別適用于智能家居等物聯網系統。
三、MQTT 服務器搭建
1、下載Apollo服務器,解壓(免安裝的)。
2、進入解壓后文件夾的 bin 目錄下(例: E:\MQTT\apache-apollo-1.7.1\bin),按住 Shift 鍵,點擊鼠標右鍵選擇 "在此處打開命令窗口";
3、在命令窗口,輸入 apollo create 服務器實例名稱(例:apollo create mybroker),之后會在 bin 目錄下創建該名稱的文件夾。該文件夾中, etc\apollo.xml 文件是配置服務器信息的文件。etc\users.properties 文件包含連接 MQTT 服務器時用到的用戶名和密碼,默認為 admin=password,即賬號為admin,密碼為 password,可自行更改。
4、在命令窗口,輸入 cd xxx/bin, 進入該實例的bin目錄下,執行 apollo-broker.cmd run 命令,開啟服務器,看到如下界面代表搭建完成。
之后在瀏覽器輸入 http://127.0.0.1:61680/,查看是否安裝成功。
四、 MQTT Android 客戶端具體實現
1、在 module 的 build.gradle 添加依賴
repositories {
maven {
url "https://repo.eclipse.org/content/repositories/paho-releases/"
}
}
dependencies {
compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.0'
}
2、添加權限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
3、基本字段說明
topic:中文意思是“話題”。在 MQTT 中訂閱了( subscribe )同一話題(topic)的客戶端會同時收到消息推送。直接實現了“群聊”功能。
clientId:客戶身份唯一標識。
qos:服務質量。
retained:要保留最后的斷開連接信息。
MqttAndroidClient#subscribe():訂閱某個話題。
MqttAndroidClient#publish(): 向某個話題發送消息,之后服務器會推送給所有訂閱了此話題的客戶。
userName:連接到MQTT服務器的用戶名。
passWord :連接到MQTT服務器的密碼。
4、注冊Service
<!-- Mqtt Service -->
<service android:name="org.eclipse.paho.android.service.MqttService" />
<service android:name="com.mqtt.demo.MQTTService"/>
5、Android 端具體實現
public class MQTTService extends Service {
public static final String TAG = MQTTService.class.getSimpleName();
private static MQTTService instance;
private static MqttAndroidClient client;
private MqttConnectOptions conOpt;
private String host = "tcp://192.168.7.31:61613";
private String userName = "admin";
private String passWord = "password";
private static String myTopic = "topic";
private String clientId = "test2";
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
init();
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
instance = null;
try {
client.disconnect();
client.unregisterResources();
} catch (MqttException e) {
e.printStackTrace();
}
super.onDestroy();
}
public static void publish(String msg){
String topic = myTopic;
Integer qos = 0;
Boolean retained = false;
try {
client.publish(topic, msg.getBytes(), qos.intValue(), retained.booleanValue());
} catch (MqttException e) {
e.printStackTrace();
}
}
// MQTT監聽連接情況
private IMqttActionListener iMqttActionListener = new ActionListener();
// MQTT監聽并且接受消息
private MqttCallback mqttCallback = new CallBack();
private void init() {
clientId = MacAddressUtil.getLocalMacAddress(this);
// 服務器地址(協議+地址+端口號)
String uri = host;
client = new MqttAndroidClient(this, uri, clientId);
// 設置MQTT監聽并且接受消息
client.setCallback(mqttCallback);
conOpt = new MqttConnectOptions();
// 清除緩存
conOpt.setCleanSession(true);
// 設置超時時間,單位:秒
conOpt.setConnectionTimeout(10);
// 心跳包發送間隔,單位:秒
conOpt.setKeepAliveInterval(20);
// 用戶名
conOpt.setUserName(userName);
// 密碼
conOpt.setPassword(passWord.toCharArray());
// last will message
boolean doConnect = true;
String message = "{\"terminal_uid\":\"" + clientId + "\"}";
String topic = myTopic;
Integer qos = 0;
Boolean retained = false;
if (TextUtils.isEmpty(message) || TextUtils.isEmpty(topic)) {
// 最后發送的消息
try {
conOpt.setWill(topic, message.getBytes(), qos.intValue(), retained.booleanValue());
} catch (Exception e) {
Log.i(TAG, "Exception Occured", e);
doConnect = false;
iMqttActionListener.onFailure(null, e);
}
}
if (doConnect) {
doClientConnection();
}
}
/** 連接MQTT服務器 */
private void doClientConnection() {
if (!client.isConnected() && isConnectIsNomarl()) {
try {
client.connect(conOpt, null, iMqttActionListener);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
/** 判斷網絡是否連接 */
private boolean isConnectIsNomarl() {
ConnectivityManager connectivityManager = (ConnectivityManager) this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable()) {
String name = info.getTypeName();
Log.i(TAG, "MQTT當前網絡名稱:" + name);
return true;
} else {
Log.i(TAG, "MQTT 沒有可用網絡");
return false;
}
}
public static boolean isConnect(){
if (instance != null && client != null){
return instance.client.isConnected();
}
return false;
}
public static void connect(){
if (instance != null && client != null){
instance.doClientConnection();
}
}
public static void disconnect(){
if (instance != null){
try {
instance.client.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
}
}
public static class ActionListener implements IMqttActionListener {
@Override
public void onSuccess(IMqttToken arg0) {
Log.i(TAG, "連接成功 ");
try {
// 訂閱myTopic話題
client.subscribe(myTopic,1);
} catch (MqttException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(IMqttToken arg0, Throwable arg1) {
arg1.printStackTrace();
// 連接失敗,重連
Log.i("mtqq", "onFailure");
}
}
public static class CallBack implements MqttCallback{
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String str1 = new String(message.getPayload());
MQTTMessage msg = new MQTTMessage();
msg.setMessage(str1);
EventBus.getDefault().post(msg);
String str2 = "topic:" + topic + ", qos:" + message.getQos() + ", retained:" + message.isRetained();
Log.i(TAG, "messageArrived:" + str1);
Log.i(TAG, str2);
}
@Override
public void deliveryComplete(IMqttDeliveryToken arg0) {
Log.i("mtqq", "deliveryComplete");
}
@Override
public void connectionLost(Throwable arg0) {
// 失去連接,重連
String message = "null";
if (arg0 != null) {
message = arg0.getMessage();
}
Log.i("mtqq", "connectionLost:"+message);
}
}
}
5、手機顯示
6、服務端顯示
打開服務端http://127.0.0.1:61680/,看到的是這個樣子
7、例子代碼下載
https://github.com/jkjk66/AndroidMQTT.git