在Android中使用 MQTT 服务实现消息通信

1.摘要


MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是一种轻量级的、基于发布/订阅(Publish/Subscribe)模式的通信协议,最初由 IBM 在1999年开发。它设计用于在低带宽、不稳定的网络环境下进行通信,适用于物联网(IoT)和机器对机器(M2M)通信。

2.准备工作


在项目的 build.gradle 文件中添加 MQTT 相关的依赖库:

groovy 复制代码
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'

添加权限:

xml 复制代码
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />

3.代码实现


kotlin 复制代码
class MqttService: Service() {
    private val TAG = "MqttService"

    private lateinit var mqttAndroidClient: MqttAndroidClient
    private lateinit var mqttConnectOptions: MqttConnectOptions
    private val serverUri = "tcp://YOUR_BROKER_URI:PORT"// 修改为你的 MQTT 服务器地址
    private val clientId = "AndroidClient"// 修改为你的客户端 ID
    private val username = "YOUR_USERNAME"// 修改为你的用户名
    private val password = "YOUR_PASSWORD"// 修改为你的密码

    private val PUBLISH_TOPIC = "YOUR_PUBLISH_TOPIC"

    private lateinit var handler: Handler
    private val retryInterval: Long = 5000 // 重试间隔,单位:毫秒(5秒)


    override fun onCreate() {
        super.onCreate()
        handler = Handler()
        initializeMqttClient()
    }

    // 初始化 MQTT 客户端
    private fun initializeMqttClient() {
        mqttAndroidClient = MqttAndroidClient(applicationContext, serverUri, clientId)
        mqttConnectOptions = MqttConnectOptions().apply {
            userName = username  // 用户名
            password = this@MqttService.password.toCharArray()  // 密码
            isAutomaticReconnect = true //是否自动尝试重新连接
            isCleanSession = false // 清除缓存
            connectionTimeout = 10  // 设置超时时间,单位:秒
            keepAliveInterval = 20  // 心跳包发送间隔,单位:秒
            //当客户端与 MQTT 代理建立连接时,客户端可以指定一个遗嘱消息。如果客户端在建立连接后因为某种原因(例如网络故障或客户端异常退出)非正常断开连接,MQTT 代理将会发布这条遗嘱消息给所有订阅了客户端所订阅主题的客户端。这样,其他订阅者就可以知道客户端已经离线了。
            //Topic:遗嘱消息要发布到的主题。
            //Payload:遗嘱消息的内容。
            //QoS(Quality of Service):遗嘱消息的传输质量要求。
            //Retained:指定是否将遗嘱消息保留在 MQTT 代理中,以便新订阅者在连接时立即接收到该消息。
            setWill(PUBLISH_TOPIC, "I am offline".toByteArray(), 1, false)//设置遗嘱消息
        }

        mqttAndroidClient.setCallback(object : MqttCallbackExtended {
            override fun connectComplete(reconnect: Boolean, serverURI: String?) {
                Log.d(TAG, "连接到: $serverURI")
            }

            override fun connectionLost(cause: Throwable?) {
                Log.d(TAG, "连接丢失: ${cause?.message}")
            }

            override fun messageArrived(topic: String?, message: MqttMessage?) {
                Log.d(TAG, "消息到达: $topic - ${message.toString()}")
            }

            //消息传递完成时的回调函数
            override fun deliveryComplete(token: IMqttDeliveryToken?) {
                Log.d(TAG, "Delivery complete")
            }
        })

        connectToMqttBroker()
    }

    // 连接到 MQTT 代理
    private fun connectToMqttBroker() {
        try {
            if (isNetworkConnected()) {
                mqttAndroidClient.connect(mqttConnectOptions, null, object : IMqttActionListener {
                    override fun onSuccess(asyncActionToken: IMqttToken?) {
                        Log.d(TAG, "连接到MQTT代理")
                        //订阅主题
                        subscribeToTopic(PUBLISH_TOPIC)
                    }

                    override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                        Log.e(TAG, "连接MQTT代理失败: ${exception?.message}")
                        //因为设置了 isAutomaticReconnect 为 true 所以不需要手动重连操作,mqtt会自动重连
                    }
                })
            } else {
                Log.d(TAG, "无网络连接")
                retryConnectToMqttBroker()
            }
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    // 重连机制
    private fun retryConnectToMqttBroker() {
        handler.postDelayed({
            Log.d(TAG, "重试连接到MQTT代理")
            connectToMqttBroker()
        }, retryInterval)
    }

    // 发布消息
    fun publishMessage(topic: String, message: String) {
        try {
            if (mqttAndroidClient.isConnected) {
                val mqttMessage = MqttMessage()
                mqttMessage.payload = message.toByteArray()
                mqttAndroidClient.publish(topic, mqttMessage)
                Log.d(TAG, "消息发布到主题: $topic")
            } else {
                Log.e(TAG, "客户端未连接,无法发布消息.")
            }
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    // 订阅主题
    fun subscribeToTopic(topic: String) {
        try {
            if (mqttAndroidClient.isConnected) {
                mqttAndroidClient.subscribe(topic, 1, null, object : IMqttActionListener {
                    override fun onSuccess(asyncActionToken: IMqttToken?) {
                        Log.d(TAG, "订阅主题: $topic")
                    }

                    override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                        Log.e(TAG, "订阅主题失败: $topic")
                    }
                })
            } else {
                Log.e(TAG, "客户端未连接,无法订阅主题.")
            }
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    // 取消订阅主题
    fun unsubscribeFromTopic(topic: String) {
        try {
            if (mqttAndroidClient.isConnected) {
                mqttAndroidClient.unsubscribe(topic, null, object : IMqttActionListener {
                    override fun onSuccess(asyncActionToken: IMqttToken?) {
                        Log.d(TAG, "取消订阅主题: $topic")
                    }

                    override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                        Log.e(TAG, "未能取消订阅主题: $topic")
                    }
                })
            } else {
                Log.e(TAG, "客户端未连接,无法取消订阅主题.")
            }
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    // 判断网络是否连接
    private fun isNetworkConnected(): Boolean {
        val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        return activeNetwork?.isConnected == true
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onDestroy() {
        super.onDestroy()
        disconnectClient()
    }

    // 断开 MQTT 客户端连接
    private fun disconnectClient() {
        try {
            if (mqttAndroidClient.isConnected) {
                mqttAndroidClient.disconnect()
            }
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }
}

4.开启服务


在AndroidManifest.xml中的application标签内注册服务

xml 复制代码
 <service android:name=".MqttService" />
 <service android:name="org.eclipse.paho.android.service.MqttService" />

5.功能点介绍


MQTT 客户端初始化:
  • 通过 MqttAndroidClient 和 MqttConnectOptions 初始化 MQTT 客户端。
  • 配置 MQTT 客户端的连接选项,包括用户名、密码、自动重连、清除会话、超时设置和心跳包发送间隔等。
  • 配置遗嘱消息,当客户端非正常断开时,MQTT 代理将发布该消息(setWill 方法)。
MQTT 客户端回调:
  • 实现 MqttCallbackExtended 接口,用于处理连接完成、连接丢失、消息到达以及消息传递完成时的回调。.
连接到 MQTT 代理:
  • 尝试连接到 MQTT 代理,如果网络连接正常则进行连接,并订阅指定的主题(PUBLISH_TOPIC)。
  • 如果连接失败且设置了自动重连选项,客户端会自动尝试重连。
  • 如果没有网络连接,则通过重试机制定时尝试重新连接。
消息发布:
  • 实现 publishMessage 方法,用于向指定主题发布消息。
订阅和取消订阅主题:
  • 实现 subscribeToTopic 方法,用于订阅指定的主题。
  • 实现 unsubscribeFromTopic 方法,用于取消订阅指定的主题。

6.其他介绍


MQTT中 QoS 级别的意义:

QoS 0:最多一次传递(At most once)

  • 也称为"至多一次"传递
  • 发布消息后,不会收到任何确认或回执。
  • MQTT 代理不会跟踪消息传递,也不会重新传递丢失的消息。
  • 此级别适用于那些可以容忍偶尔丢失消息的应用场景,例如实时数据流,传感器数据等。

QoS 1:至少一次传递(At least once)

  • 确保消息至少被传递一次。
  • 在发布消息后,发布者会收到一个 PUBACK(发布确认)消息作为回复。
  • 如果 PUBACK 丢失或未收到确认,则 MQTT 客户端会尝试重新发送消息,直到收到 PUBACK。
  • 此级别适用于对消息到达的顺序不是特别关心,但确保消息至少被传递一次的应用场景。

QoS 2:只有一次传递(Exactly once)

  • 提供最高的传递保证,确保每条消息只传递一次。
  • 在发布消息后,发布者会收到两个确认消息:PUBREC(发布接收)和 PUBREL(发布释放)。
  • MQTT 客户端会等待收到 PUBREL 消息后再发送 PUBCOMP(发布完成)消息作为最终确认。
  • 此级别适用于对消息传递的顺序和确保每条消息只传递一次的高度敏感的应用场景,如金融交易、命令和控制等。
MQTT断开连接((32109) - java.io.EOFException)

可以看我的这篇文章:MQTT断开连接((32109) - java.io.EOFException)

7.最后


MQTT在物联网开发中必不可少,掌握相关知识非常重要,此篇文章用来温故一下MQTT使用流程,知识点不多,代码已经封装的差不多了,方便本人及各位拿来即用 更多功能自行拓展。

相关推荐
爱数学的程序猿4 分钟前
Python入门:6.深入解析Python中的序列
android·服务器·python
brhhh_sehe23 分钟前
重生之我在异世界学编程之C语言:深入文件操作篇(下)
android·c语言·网络
zhangphil28 分钟前
Android基于Path的addRoundRect,Canvas剪切clipPath简洁的圆形图实现,Kotlin(2)
android·kotlin
Calvin8808281 小时前
Android Studio 的革命性更新:Project Quartz 和 Gemini,开启 AI 开发新时代!
android·人工智能·android studio
敲代码敲到头发茂密2 小时前
【大语言模型】LangChain 核心模块介绍(Memorys)
android·语言模型·langchain
H1003 小时前
重构(二)
android·重构
拓端研究室3 小时前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil4 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
m0_512744644 小时前
极客大挑战2024-web-wp(详细)
android·前端
lw向北.5 小时前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt