一、关于 MQTT
MQTT 是 OASIS 标准中面向物联网 (IoT) 的消息传递协议,它被设计成一种极其轻量级的发布/订阅消息传输方式,非常适合以小的代码占用和极低的网络带宽连接远程设备。如今,MQTT 已广泛应用于汽车、制造、电信、石油天然气等众多行业,是物联网消息传递标准。

1.1 Eclipse Paho
Paho 项目旨在为机器对机器 (M2M) 和物联网 (IoT) 领域的新兴、现有及未来应用提供可靠的开源实现,这些实现基于开放且标准的即时通讯协议。Paho 充分考虑了设备连接固有的物理和成本限制,其目标包括有效实现设备和应用之间的解耦,旨在保持市场开放,并促进可扩展的 Web 和企业中间件及应用的快速发展。
Paho 包含用于嵌入式平台的MQTT发布/订阅客户端实现,以及社区确定的相应服务器支持。Paho Java Client 是一个用 Java 编写的 MQTT 客户端库,用于开发运行在 JVM 或其他 Java 兼容平台(例如 Android)上的应用程序。

1.2 MQTT.fx
MQTT.fx® 5 是我们用于测试开发和生产环境中物联网路由的工具。它允许您连接到开发和/或生产代理,并在编写代码之前测试您的项目。由于无需编程即可运行这些测试用例,这不仅意味着可以显著减少您的手动工作量,而且还能显著提高软件质量。
MQTT.fx 是一款基于Eclipse Paho,使用Java语言编写的MQTT客户端工具。支持通过Topic订阅和发布消息,用来前期和物理云平台调试非常方便。

二、基于Android的MQTT网络架构
设计一个基于Android的MQTT网络架构,核心在于解耦 、健壮性 (断连重连)和易用性。
我们将采用 单例模式 (Singleton) 结合 观察者模式 (Observer) 的设计。底层使用成熟的 Eclipse Paho Android Client 库,因为它提供了对Android Service的封装,能够更好地处理后台运行和心跳保活。
2.1 架构设计概览
- MqttManager (Core) : 单例核心类,负责管理
MqttAndroidClient实例,处理连接、断开、重连逻辑。 - IMqttEventListener: 接口,用于业务层接收消息、连接状态变化。
- TopicDispatcher: 内部逻辑,将收到的MQTT消息根据Topic分发给不同的监听器。
- Config: 独立的配置参数,便于项目移植。
2.2 依赖准备 (build.gradle)
在 app/build.gradle 中添加 Paho MQTT 依赖(注意:Paho Android Service 需要添加 localbroadcastmanager 和 support-v4 的相关支持,或者使用兼容版本):
groovy
dependencies {
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
// 如果是AndroidX项目,可能需要添加此依赖以支持Paho Service
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
}
在 AndroidManifest.xml 中注册 Service:
xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application ...>
<!-- MqttService -->
<service android:name="org.eclipse.paho.android.service.MqttService" />
</application>
2.3 核心代码实现 (Java)
2.3.1 定义回调接口 (IMqttEventListener.java)
这个接口用于统一业务层接收消息和状态。
java
package com.example.mqtt.core;
public interface IMqttEventListener {
// 收到消息
void onMessageArrived(String topic, String message);
// 连接成功
void onConnected();
// 连接断开(包含异常断开)
void onDisconnected(String reason);
}
2.3.2 MQTT 管理核心类 (MqttManager.java)
这是最关键的模块,封装了连接配置、自动重连、消息分发和线程切换(确保回调在主线程)。
java
package com.example.mqtt.core;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* MQTT 管理单例
* 负责统一连接、断开、发布、订阅以及网络状态处理
*/
public class MqttManager {
private static final String TAG = "MqttManager";
private static MqttManager instance;
private MqttAndroidClient client;
private Context context;
// 保存每个Topic对应的订阅列表(用于断线重连后自动重新订阅)
private final List<String> subscribedTopics = new ArrayList<>();
// 消息监听器列表(支持多处订阅)
private final List<IMqttEventListener> eventListeners = new ArrayList<>();
// 主线程Handler,用于将消息抛回UI线程
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private MqttManager() {}
public static MqttManager getInstance() {
if (instance == null) {
synchronized (MqttManager.class) {
if (instance == null) {
instance = new MqttManager();
}
}
}
return instance;
}
/**
* 初始化并连接
* @param context 上下文
* @param brokerUrl 服务器地址 (tcp://xxx:1883)
* @param clientId 客户端ID
* @param username 用户名 (可选)
* @param password 密码 (可选)
*/
public void init(Context context, String brokerUrl, String clientId, String username, String password) {
this.context = context.getApplicationContext();
if (client != null && client.isConnected()) {
disconnect();
}
client = new MqttAndroidClient(this.context, brokerUrl, clientId);
// 设置回调
client.setCallback(new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
Log.d(TAG, "Connected! Reconnect=" + reconnect);
if (reconnect) {
//如果是自动重连成功,需要重新订阅之前的Topic
reSubscribeAllTopics();
}
notifyConnected();
}
@Override
public void connectionLost(Throwable cause) {
Log.w(TAG, "Connection Lost", cause);
notifyDisconnected(cause != null ? cause.getMessage() : "Unknown");
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String msgContent = new String(message.getPayload());
Log.d(TAG, "Msg Arrived: [" + topic + "] " + msgContent);
notifyMessageArrived(topic, msgContent);
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// 消息发送成功确认,如有需要可处理
}
});
connect(username, password);
}
private void connect(String username, String password) {
MqttConnectOptions options = new MqttConnectOptions();
// 自动重连是Paho的关键特性,处理网络不稳定
options.setAutomaticReconnect(true);
// 清除会话:false表示服务器会保留之前的订阅信息和未接收的消息(QoS>0)
// 建议设为true,由客户端在connectComplete中手动控制订阅,逻辑更清晰
options.setCleanSession(true);
options.setConnectionTimeout(30); // 超时时间
options.setKeepAliveInterval(60); // 心跳间隔
if (username != null && !username.isEmpty()) {
options.setUserName(username);
options.setPassword(password.toCharArray());
}
try {
client.connect(options, null, new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
// 首次连接成功由 connectComplete 回调处理
Log.d(TAG, "Connect Action Success");
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.e(TAG, "Connect Failed", exception);
notifyDisconnected("Initial Connect Failed: " + exception.getMessage());
}
});
} catch (MqttException e) {
e.printStackTrace();
}
}
/**
* 订阅主题
* @param topic 主题
* @param qos 服务质量 (0, 1, 2)
*/
public void subscribe(String topic, int qos) {
if (client != null && client.isConnected()) {
try {
client.subscribe(topic, qos);
if (!subscribedTopics.contains(topic)) {
subscribedTopics.add(topic);
}
Log.d(TAG, "Subscribed to: " + topic);
} catch (MqttException e) {
Log.e(TAG, "Subscribe Error", e);
}
} else {
// 如果当前未连接,记录下来,等连接成功后自动订阅
if (!subscribedTopics.contains(topic)) {
subscribedTopics.add(topic);
}
Log.w(TAG, "Client not connected, topic added to pending list: " + topic);
}
}
/**
* 发布消息
*/
public void publish(String topic, String msg, int qos, boolean retained) {
if (client != null && client.isConnected()) {
try {
MqttMessage message = new MqttMessage();
message.setPayload(msg.getBytes());
message.setQos(qos);
message.setRetained(retained);
client.publish(topic, message);
Log.d(TAG, "Published to " + topic + ": " + msg);
} catch (MqttException e) {
Log.e(TAG, "Publish Error", e);
}
} else {
Log.e(TAG, "Cannot publish, client not connected.");
}
}
/**
* 断开连接
*/
public void disconnect() {
if (client != null) {
try {
client.disconnect();
client = null;
subscribedTopics.clear();
Log.d(TAG, "Disconnected manually");
} catch (MqttException e) {
e.printStackTrace();
}
}
}
/**
* 注册全局监听器
*/
public void registerListener(IMqttEventListener listener) {
if (!eventListeners.contains(listener)) {
eventListeners.add(listener);
}
}
/**
* 移除监听器
*/
public void unregisterListener(IMqttEventListener listener) {
eventListeners.remove(listener);
}
// --- 内部辅助方法 ---
private void reSubscribeAllTopics() {
for (String topic : subscribedTopics) {
try {
client.subscribe(topic, 1);
Log.d(TAG, "Re-subscribed: " + topic);
} catch (MqttException e) {
Log.e(TAG, "Re-subscribe failed for " + topic, e);
}
}
}
private void notifyMessageArrived(String topic, String message) {
mainHandler.post(() -> {
for (IMqttEventListener listener : eventListeners) {
listener.onMessageArrived(topic, message);
}
});
}
private void notifyConnected() {
mainHandler.post(() -> {
for (IMqttEventListener listener : eventListeners) {
listener.onConnected();
}
});
}
private void notifyDisconnected(String reason) {
mainHandler.post(() -> {
for (IMqttEventListener listener : eventListeners) {
listener.onDisconnected(reason);
}
});
}
public boolean isConnected() {
return client != null && client.isConnected();
}
}
2.4. 项目中的使用方法
你只需要在项目中按照以下步骤调用即可。
2.4.1 初始化 (通常在 Application 或 MainActivity)
java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 替换你的配置
String brokerUrl = "tcp://broker.emqx.io:1883";
String clientId = "Android_App_" + System.currentTimeMillis();
// 初始化并连接
MqttManager.getInstance().init(this, brokerUrl, clientId, null, null);
}
}
2.4.2 订阅和接收消息 (在 Activity 或 ViewModel 中)
java
public class MainActivity extends AppCompatActivity implements IMqttEventListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 注册监听
MqttManager.getInstance().registerListener(this);
// 2. 订阅主题 (可以在连接成功回调里做,也可以直接调用,Manager内部做了缓存处理)
MqttManager.getInstance().subscribe("device/sensor/data", 1);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 3. 必须移除监听,防止内存泄漏
MqttManager.getInstance().unregisterListener(this);
}
// --- 接口回调实现 ---
@Override
public void onMessageArrived(String topic, String message) {
// 这里已经是主线程,可以直接更新UI
if ("device/sensor/data".equals(topic)) {
// 处理具体的业务逻辑,例如解析JSON
System.out.println("Received: " + message);
}
}
@Override
public void onConnected() {
Toast.makeText(this, "MQTT Connected", Toast.LENGTH_SHORT).show();
}
@Override
public void onDisconnected(String reason) {
Toast.makeText(this, "MQTT Disconnected: " + reason, Toast.LENGTH_SHORT).show();
}
// --- 按钮点击发布消息 ---
public void onSendClick(View view) {
MqttManager.getInstance().publish("device/control", "{\"cmd\":\"ON\"}", 1, false);
}
}
2.5 设计亮点与可维护性分析
- 自动重连机制 (
setAutomaticReconnect):
- 代码中配置了Paho自带的自动重连。当网络从断开变为连接(如4G切换WiFi,或进入电梯后出来),Paho Client 会尝试重连。
- 关键点 : 我们使用了
MqttCallbackExtended。当重连成功时,会触发connectComplete(boolean reconnect, ...)。代码中通过判断reconnect标志,自动执行reSubscribeAllTopics(),解决了**"重连后丢失之前订阅Topic"**的常见痛点。
-
统一的订阅管理 (
subscribedTopicsList) :即使用户在网络未连接时调用了
subscribe,我们也会将其加入列表。一旦连接建立,或者重连成功,管理器会自动遍历列表进行实际订阅。业务层无需关心当前是否在线。 -
线程安全与UI交互 :
MQTT的回调通常在后台线程。
MqttManager内部持有一个Handler(Looper.getMainLooper()),确保分发给IMqttEventListener的事件都在主线程 执行。业务层更新UI时不需要再写runOnUiThread。 -
易扩展性 :
如果未来需要解析复杂的JSON消息,可以在
MqttManager的messageArrived中引入一个MessageParser接口,或者直接在业务层的onMessageArrived中通过 Topic 字符串进行switch-case分流处理。 -
生命周期安全 :
使用
ApplicationContext初始化Client,避免持有 Activity 的 Context 导致内存泄漏。提供registerListener和unregisterListener,符合Android组件生命周期规范。