简介这份Android MQTT客户端源码面向Android开发者和物联网初学者提供了可直接安装的APK上手即可运行。压缩包共56个文件体积1.62MB包含核心Java源码、编译后的class与dex、XML界面资源、第三方jar库如com.ibm.micro.client.mqttv3以及打包好的APK目录结构清晰便于对照代码与实际运行效果学习。资源核心类MqttAndroidClient封装了连接、断开、发布、订阅等操作同时覆盖连接管理、主题操作、消息回调与异常处理等关键模块配合开源服务端Mosquitto可在本地构建完整的数据通信链路也可修改源码加入身份验证、加密传输等高级特性。已有1473人学习下载适合想深入理解MQTT协议、快速搭建IoT通信应用或研究Android网络编程的开发者复用与二次开发。 做物联网或者嵌入式开发的朋友对MQTT协议应该都不陌生。最近整理了一个可以直接安装使用的Android MQTT客户端源码项目正好趁这个机会把整个实现思路、核心源码模块和踩坑记录梳理一遍。这个项目不是那种只有几个类的Demo而是一个完整的、能直接编译安装到手机上的App支持连接配置、订阅发布、遗嘱消息、断线重连等常用功能不管是拿来学习MQTT协议在Android端的落地姿势还是直接作为调试工具使用都非常顺手。项目的整体结构并不复杂但每一块都有值得展开聊的细节。下面我会从方案选型开始逐步讲清楚每个模块的设计思路然后把编译安装和联调过程完整走一遍最后把我在实际使用中遇到的典型问题和排查方法整理成速查表希望对正在做类似需求的朋友有实际帮助。1. 项目整体设计与方案选型1.1 为什么选择MQTT协议作为客户端通信方案在做Android端和硬件设备、服务器通信的方案选型时很多人的第一反应是HTTP轮询或者自建Socket长连接。这两个方案不是不行但在物联网场景下都有很明显的痛点。HTTP轮询的实时性差客户端为了拿到最新状态只能频繁请求对手机电量和服务器压力都是负担自建Socket长连接虽然实时性高但心跳机制、重连策略、消息可靠性全部要自己从头实现工作量大而且容易出坑。MQTT协议本身就是为低带宽、高延迟、网络不稳定的物联网场景设计的。它基于发布/订阅模型客户端和服务端解耦一个设备发布消息所有订阅了对应主题的客户端都能实时收到。关键是不需要客户端和服务端保持一对一的固定连接消息通过Broker中转这大大简化了多设备通信的复杂度。以停车场项目中对接海康、大华等主流车牌识别相机为例相机端通过MQTT协议把识别结果推到Broker上Android端客户端订阅对应主题就能实时拿到车牌号、入场时间等数据。如果用HTTP轮询相机的识别结果要等客户端主动来拉实时性和效率都差很多。这也是我最终确定在Android端落地MQTT客户端的原因。1.2 客户端库选型对比Android上实现MQTT客户端目前比较成熟的选择有下面几种我用表格列一下它们的核心差异客户端库维护状态支持协议版本特点Eclipse Paho Android官方持续维护MQTT 3.1 / 3.1.1 / 5.0老牌稳定社区活跃资料多Android兼容性好HiveMQ MQTT Client社区活跃MQTT 3.1 / 3.1.1 / 5.0API简洁没有依赖Java 8后台线程管理优秀EMQX Android SDK支持维护MQTT 5.0面向EMQX Broker深度优化集成了SSL等能力我最终选择的是Eclipse Paho Android原因有三个一是它被Android官方文档和大量开源项目引用经过了充分的线上验证二是它封装了Android Service组件自带断线重连和状态回调机制不需要自己管理后台线程三是资料和Stack Overflow上的案例非常多遇到问题基本都能找到解决方案。需要说明的是这个项目源码里也保留了一层自己的封装MqttManager目的是把Paho的异步API进一步包装成更符合业务直觉的接口同时把连接状态、订阅关系、消息回调集中管理。这样即使以后要换底层库业务层代码基本不用动。2. 核心源码模块与实现细节2.1 工程目录和职责划分整个项目的包结构遵循了比较常规的单模块App结构核心包如下src/main/java/com/example/mqttclient/ ├── MainActivity.java // 主界面操作入口 ├── MqttManager.java // MQTT核心管理者封装连接、订阅、发布 ├── MqttService.java // 后台服务持有连接处理生命周期 ├── ConnectionConfig.java // 连接参数配置实体类 ├── PreferencesManager.java // 配置本地持久化 ├── ToastUtil.java // 轻提示工具 └── MessageAdapter.java // 消息列表展示适配器这个结构的核心思路是MainActivity只负责UI交互和展示MqttService负责在后台保持连接MqttManager是所有操作的唯一入口。实际使用中你会发现把连接和UI彻底分离非常重要因为MQTT连接的生命周期要长于任何一个Activity在手机息屏甚至App切到后台时连接仍然需要保持。2.2 MqttManager连接管理模块连接管理是整个客户端最核心的模块涉及参数组装、连接发起、状态回调、断线重连几个部分。先看连接参数的关键项serverURIBroker地址格式为tcp://ip:port或ssl://ip:port本地调试用前者公网场景建议用SSL加密传输。clientId客户端唯一标识必须保证在同一Broker上唯一。如果两个客户端使用相同clientId后连接的会把先连接的踢下线。cleanSession是否清理会话。设为false时Broker会保存离线消息和订阅关系设备重连后能收到离线期间的消息设为true则每次连接等同于全新会话。实际项目中这个参数我一般是做成开关默认true需要离线消息保障时再打开。connectionTimeout连接超时时间建议10秒到30秒之间。keepAliveInterval心跳保活间隔这个参数非常关键下面单独说。连接代码的核心片段如下MqttConnectOptions options new MqttConnectOptions(); options.setCleanSession(config.isCleanSession()); options.setConnectionTimeout(config.getConnectionTimeout()); options.setKeepAliveInterval(config.getKeepAliveInterval()); options.setAutomaticReconnect(true); if (config.getUsername() ! null) { options.setUserName(config.getUsername()); options.setPassword(config.getPassword().toCharArray()); } if (config.getWillTopic() ! null) { options.setWill(config.getWillTopic(), config.getWillMessage().getBytes(), 1, false); } mqttClient.connect(options, null, new IMqttActionListener() { Override public void onSuccess(IMqttToken asyncActionToken) { // 连接成功更新UI状态并重新订阅已保存的主题 } Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { // 连接失败记录日志并提示用户 } });这里重点说两个容易被忽略的点。第一是setAutomaticReconnect(true)。Paho的自动重连机制开启后客户端检测到连接断开会自动按退避策略重连不需要我们自己写循环。这个机制的前提是MqttClient实例在断开后没有被销毁。所以我在MqttService里持有全局唯一的MqttClient实例避免Activity重建导致连接对象被GC。第二是再强调一下keepAliveInterval。它表示客户端在没有任何消息发送时每隔多久发送一次心跳PING包。设置太长Broker要很久才能发现设备掉线设置太短心跳包太频繁会浪费流量。我实测下来在移动网络环境下30到60秒是一个比较合理的区间在WiFi环境下可以放宽到60到120秒。2.3 消息订阅与发布实现订阅和发布操作同样通过MqttManager统一暴露业务层不需要关心底层API的异步回调细节。订阅一个主题的核心片段public void subscribe(String topic, int qos) throws MqttException { if (mqttClient null || !mqttClient.isConnected()) { throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED); } mqttClient.subscribe(topic, qos, null, new IMqttActionListener() { Override public void onSuccess(IMqttToken asyncActionToken) { // 订阅成功保存到已订阅主题集合方便重连后自动恢复 } Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { // 订阅失败处理 } }); }这里有个实际经验重连后要主动恢复订阅。虽然设置cleanSessionfalse时Broker会保存订阅关系但实际上很多Broker在客户端断线后如果超过了一定时间比如EMQX默认的会话过期时间会把订阅关系清理掉。为了保证可靠性我在连接成功的onSuccess回调里读取之前保存的订阅主题集合重新执行一遍subscribe。这样即使Broker侧丢失了会话客户端也能自动恢复。发布消息时要特别关注QoS等级的选择。QoS 0是至多一次适合传感器数据、日志等允许丢失的场景QoS 1是至少一次消息可能重复但不会丢失QoS 2是恰好一次最可靠但开销最大。在实际项目中控制类消息建议至少QoS 1状态上报类可以根据业务容忍度在0和1之间选。QoS 2因为握手过程复杂、吞吐量低除非是计费、订单这类强一致场景否则一般用不到。2.4 遗嘱消息机制源码里还实现了遗嘱消息Last Will功能这是一个很容易被忽略但非常实用的特性。遗嘱消息机制的原理是客户端在发起连接时通过setWill告诉Broker如果我异常掉线比如网络断开、设备崩溃请帮我向指定主题发布这条预设消息。这样其他订阅了该主题的客户端就能立刻感知到设备掉线状态。在App源码里的具体做法是ConnectionConfig里增加willTopic和willMessage两个字段连接时写入MqttConnectOptions。测试时可以用两个客户端验证一个订阅/status主题另一个正常连接后直接断网订阅方会立刻收到一条offline消息。这个功能在设备在线状态监控场景里非常好用。3. Android工程配置与打包安装实操3.1 开发环境准备源码默认的编译环境是Android Studio Hedgehog2023.1.1及以上版本JDK 11Gradle 8.xSDK版本方面compileSdk设为34minSdk设为21覆盖了Android 5.0以上所有主流机型。项目根目录的build.gradle里已经配置好了Eclipse Paho Android Service依赖implementation org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5 implementation org.eclipse.paho:org.eclipse.paho.android.service:1.1.1这里有一点需要注意org.eclipse.paho.android.service这个库依赖较老在Android 12以上的系统上需要在AndroidManifest.xml中对Service组件做显式声明并加上android:exportedfalse否则安装运行时会报错。源码工程里已经处理好了这个配置如果是从旧项目迁移记得检查这一项。另外如果Broker用的是tcp://明文连接Android 9API 28开始默认禁止明文流量需要在AndroidManifest.xml的application节点下加上android:usesCleartextTraffictrue如果追求更安全的做法可以在network_security_config.xml中只对特定域名放开明文流量而不是全局开启。3.2 编译打包APK编译过程不需要额外配置用Android Studio打开工程后等待Gradle同步完成依次点击Build - Build APK(s) - Build APK(s)就会在app/build/outputs/apk/debug/目录下生成可安装的APK。如果想生成Release包需要先配置签名文件否则默认使用的是调试签名。生成APK之后可以通过adb install命令直接安装到手机adb install -r app/build/outputs/apk/debug/app-debug.apk也可以把APK文件直接传到手机里点击安装包安装。需要注意的是Android 8.0以上系统默认不允许安装未知来源应用需要在系统设置中允许该渠道安装应用否则安装过程会被拦截。我用这个方式在多个机型上做过测试覆盖了从Android 7到Android 14的设备目前没有发现兼容性问题。项目本身没有用到任何系统级API也不涉及Root权限普通用户拿到源码后也可以直接编译使用。3.3 配置持久化与界面操作客户端在第一次启动时用户需要手动填写Broker地址、端口、可选账号密码等信息。这些配置通过PreferencesManager保存到SharedPreferences中下次启动自动加载不用重复填写。界面支持的操作包括输入Broker地址和端口默认1883点击连接/断开输入主题和QoS点击订阅/取消订阅输入主题和消息内容点击发布查看历史消息列表显示来源主题、QoS和消息内容这个App实测下来作为调试工具比在电脑上打开MQTTX更贴近真实移动网络环境尤其是测试弱网场景下的连接稳定性时非常有用。4. 核心功能测试与场景联调4.1 本地Broker快速搭建客户端源码拿到手之后需要一个Broker才能开始联调。最省事的办法是直接用Docker跑一个EMQX实例docker run -d --name emqx -p 18083:18083 -p 1883:1883 emqx/emqx:5.0.26启动完成后Broker的MQTT端口是1883Dashboard管理界面端口是18083浏览器打开http://localhost:18083就可以看到控制台默认账号admin密码public。如果机器上没有Docker也可以下载EMQX的二进制安装包在Linux或者Windows上直接解压运行。CentOS上的部署方式官方文档写得很清楚基本上就是下载包、解压、执行bin/emqx start三步不需要额外配置即可启动一个可用的Broker。4.2 手机端与Broker联调Broker启动后确保手机和Broker所在机器处在同一局域网打开App填写Broker所在机器的局域网IP和1883端口点击连接正常情况下可以看到连接状态变为已连接。然后用MQTTX这个桌面客户端Broker环境也可以用同时连接同一个Broker在MQTTX里订阅test/topic主题在Android App的发布区域输入相同主题和内容点击发布MQTTX这边应该能实时收到消息。反向操作也一样在MQTTX里发布消息Android App的订阅区域选择test/topic消息列表会立刻刷新。这个双向测试能把客户端发布和订阅两条链路都完整验证一遍。我在实际测试中还会用mosquitto_sub命令行工具作为第三方参照避免两个测试端因为相同Bug而“互相确认错误”。4.3 遗嘱消息与离线感知测试遗嘱消息的测试需要双客户端配合。操作步骤是先在Android App上开启遗嘱消息功能并设置遗嘱主题比如status/device1和遗嘱内容比如offline连接Broker后在MQTTX中订阅这个主题然后把Android App的网络断开直接开飞行模式几秒后MQTTX就会收到一条offline消息。这个测试能验证Broker是否正确感知到客户端异常掉线并代为发布了遗嘱内容。实际项目中我通常用这个机制来实现设备在线状态监控设备正常退出时发送online/offline主题异常掉线时靠遗嘱兜底双保险。5. 常见问题与排查技巧实录5.1 连接失败排查思路连接失败是最常见的问题我在使用过程中总结了一套排查顺序现象可能原因排查方法一直显示连接中最后提示超时网络不通、端口被防火墙拦截ping一下Broker地址再用telnet ip 1883测试端口连通性提示Connecting to broker failedBroker地址或端口填错在浏览器里打开Broker的Dashboard确认端口正常提示ClientId is already in use有另一个客户端使用了相同clientId换一个不重复的clientId连接成功但发消息没反应发布主题和订阅主题不一致确认发布、订阅主题完全一致MQTT主题是区分大小写的Android 9以上无法连接明文流量被系统拦截在Manifest中加usesCleartextTraffic或配置网络安全策略从我的经验来看90%的连接问题都出在网络上而不是代码上。尤其是本地联调时一定要先确认手机能访问到Broker所在机器的IP和端口很多朋友在电脑上开了一堆防火墙规则手机连不上就怀疑代码有Bug绕了很多弯路。5.2 连接频繁掉线的排查连接成功后频繁掉线通常和KeepAlive设置、网络环境有关。我遇到过的案例是在办公室WiFi环境下连接稳定一到户外用4G网络就会出现周期性掉线重连。排查后发现是移动网络出口NAT超时时间比较短而我把keepAliveInterval设成了120秒导致运营商在客户端心跳间隔期内就断开了空闲连接。解决方法是把keepAliveInterval调到30秒同时开启setAutomaticReconnect(true)让Paho在检测到掉线后自动重连不需要用户手动操作。实测调整后掉线频率明显下降即使在电梯、地下车库里切换网络恢复速度也快了很多。另外还有一个优化点是连接回调里要做资源释放。在MqttService的onDestroy中主动断开连接并释放MqttClient实例避免Activity重建造成多个连接实例同时存在这个细节在源码里也做了处理。5.3 后台被杀与保活策略Android系统对后台App的限制越来越严格尤其是在国内定制ROM上App切到后台大概率会被杀进程导致MQTT连接断开。针对这个问题源码里采用了一个比较实用的方案将MqttService设为前台服务并绑定一个常驻通知栏通知。service android:name.MqttService android:enabledtrue android:exportedfalse android:stopWithTaskfalse /stopWithTask设为false用户在最近任务页划掉App时服务不会被系统一并停止。再配合前台服务的startForeground机制至少保证连接在App退到后台时不会被立即杀掉。但必须说句实话在国产ROM的激进省电策略下任何保活方案都无法做到100%不被杀。更可靠的做法是接受现实在连接断开后快速重建并在业务侧实现离线消息补偿机制比如客户端重连成功后主动拉取离线期间的增量数据。这不是源码的缺陷而是Android生态的固有约束。5.4 心跳与电量权衡的实践心得关于KeepAlive参数我在几个真实项目里踩过不少坑最后得出的经验是不要凭感觉调要根据实际网络环境和业务容忍度来定。设备在线状态检测要求高KeepAlive设短一点15到30秒只是消息通知类场景KeepAlive可以放宽到60到90秒对电量和流量的影响会更小。另外提一下Paho Android Service默认的PingSender是基于定时器实现的为了保证心跳包在Doze模式下的准确性源码里没有对它做额外改造。如果你的项目对实时性要求很高可以考虑用AlarmManager驱动心跳包发送这算是一个进阶优化方向。写在最后的几个实战体会整个项目从搭建到联调完成我最大的体会是MQTT客户端本身并不复杂真正的复杂度在于各种边界情况的处理——网络切换、进程被杀、会话恢复、消息可靠性。开源社区里好用的库很多但工程化落地时一定要自己把异常路径都走一遍直接拿起来就用往往会在线上环境踩到没有预料的坑。如果你打算基于这份源码做二次开发我建议优先关注三个方面一是把MqttManager的接口抽象得更贴近你的业务语义而不是直接暴露订阅发布的原始方法二是实现消息到达的本地持久化比如用Room把关键消息存下来避免消息只停留在内存里三是接一个像EMQX这样自带监控面板的Broker开发期能看到连接数、消息量、主题列表排查问题效率会高很多。本文还有配套的精品资源点击获取