MQTT 是用于物联网 (IoT) 的 OASIS 标准音讯传递协议。它被规划为一种极其轻量级的发布/订阅音讯传输,十分合适以较小的代码占用空间和最小的网络带宽衔接长途设备。现在,MQTT 已广泛应用于各个行业,例如轿车、制作、电信、石油和天然气等。

这是MQTT官方关于它的一段介绍,本文也将从下方思想导图中列出的五部分逐一知道下MQTT。

站在Android开发者的角度认识MQTT - 使用篇

简介

从官方的介绍中能够了解到,MQTT是一种根据发布/订阅式的轻量级协议,现在广泛用于物联网生态设备中,而且现在市面上大多数智能家居都选用了MQTT协议通信的办法。

这儿稍微提下我关于MQTT为什么适用于IOT设备的一些理解吧,关于IOT设备来说,它们是长时刻在线而且不会主动去恳求某些指令,它们履行动作都是收到指令才会去履行,这也就十分契合发布/订阅模式,IOT设备只需求订阅对应的指令主题,在客户端下发指定之后,设备一旦收到就会去履行;而且MQTT的传输音讯十分轻量,这也协助设备能够愈加高效的去接纳指令和回来指令成果;最后MQTT是支持组播送音讯的,客户端能够向同一主题下的一切设备发送指令,只需求发送一次,一切设备都能够接纳到。

MQTT中有两个关键的人物别离为MQTT客户端(Client)MQTT署理(Broker):

  • MQTT客户端:它既能够作为发布者也能够作为订阅者,可收发音讯;
  • MQTT署理:它负责接纳客户端发送的音讯,并将此音讯转发给别的一些契合条件(订阅此音讯主题)的客户端。

站在Android开发者的角度认识MQTT - 使用篇

上图便是一个简略的发送和订阅的流程,当Publisher发送了主题为A的音讯Name之后,此刻Broker会收到音讯Name,接着就会寻觅订阅者中现已订阅了主题A的一切订阅者,并将音讯Name转发给其寻觅到的订阅者,这样就达到了一个发布多个订阅的效果。

下面就进入实战环节,来看看Android是怎么运用MQTT进行音讯的传输。

创立MQTT客户端

首先需求将MQTT的依靠添加到项目傍边,添加的依靠的办法选用的是VersionCatalog,详细能够看上一篇文章:运用Google推荐的VersionCatalog办理一致版本

toml文件:
[versions]
mqtt = "1.2.4"
mqtt-android = "1.1.1"
[libraries]
mqtt = { group = "org.eclipse.paho", name = "org.eclipse.paho.client.mqttv3", version.ref = "mqtt" }
mqtt-android = { group = "org.eclipse.paho", name = "org.eclipse.paho.android.service", version.ref = "mqtt-android" }
[bundles]
mqtt = ["mqtt", "mqtt-android"]
build.gradle.kts文件:
dependencies {
	implementation(libs.bundles.mqtt)
}

这儿有一点要注意的是,假如你的项目是根据AndroidX开发,那么还需求额定添加下localbroadcastmanager的依靠,而且在gradle.properties文件中添加android.enableJetifier=true装备,这是由于org.eclipse.paho.android.service中引用了v4的localbroadcastmanager。

衔接MQTT

class MqttManager {
    companion object {
        private const val TAG = "MqttManager"
        private const val MQTT_URL = "tcp://broker.emqx.io:1883"
        private const val MQTT_USER_NAME = ""
        private const val MQTT_USER_PWD = ""
    }
    private lateinit var mqttClient: MqttAndroidClient
    fun connect(context: Context) {
        mqttClient = MqttAndroidClient(context, MQTT_URL, "android_mqtt")
        mqttClient.setCallback(object : MqttCallback {
            override fun connectionLost(cause: Throwable?) {
            }
            override fun messageArrived(topic: String?, message: MqttMessage?) {
            }
            override fun deliveryComplete(token: IMqttDeliveryToken?) {
            }
        })
        val mqttConnectOptions = MqttConnectOptions()
        mqttConnectOptions.apply {
            userName = MQTT_USER_NAME
            password = MQTT_USER_PWD.toCharArray()
        }
        mqttClient.connect(mqttConnectOptions, null, object : IMqttActionListener {
            override fun onSuccess(asyncActionToken: IMqttToken?) {
                Log.d(TAG, "onSuccess")
            }
            override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                Log.d(TAG, "onFailure: ${exception?.stackTraceToString()}")
            }
        })
    }
}

MQTT的衔接首要依靠于MqttAndroidClient对象,它的构造办法中有三个参数,context是为了它后续创立Service运用;serverURI是MQTT衔接的地址;clientId是标志客户端的id,需求确保唯一性。

MqttAndroidClient还能够设置MqttCallback回调,此回调接口内部有三个办法,效果别离为:

  • connectionLost():衔接断开丢掉了,会触发此回调办法,能够在此办法中进行重连和主题的重新订阅;
  • messageArrived():订阅主题对应的音讯送达时,会触发此回调办法,能够在此办法中处理接纳到的音讯;
  • deliveryComplete():这个回调办法首要是告诉客户端发布的音讯现已发布成功。

最后能够通过MqttAndroidClient.connect()办法进行真正的衔接动作,它接纳三个参数,MqttConnectOptions能够设置衔接时的一些装备,详细下面独自对它解说;userContext是一个可选参数,用于传递上下文;IMqttActionListener它是一个接口,首要效果是监听衔接的成果,内部有onSuccess()和onFailure()两个办法,别离表明衔接成功和失利。

接着对MqttConnectOptions独自解说下,它是对MQTT衔接时设置装备所用,通常的装备有:

  • userName/password:用户名和暗码
  • connectionTimeout:衔接超时时刻
  • cleanSession:这个装备是用于在重新衔接时,是否铲除客户端的会话状况
  • automaticReconnect:装备在断开衔接后是否主动重连,MqttAndroidClient内部维护了一个Task,主动重连敞开后会每1s重连一次
  • willMessage:此装备是遗言的意思,在客户端断开衔接之后会发送此遗言音讯,一般可用作判断某客户端现已离线
  • socketFactory:用于设置证书信息,MQTT可设置TSL和SSL认证办法,这个后续会独自详细介绍认证流程

以上便是在日常开发中常用的几个装备项。

tcp://broker.emqx.io:1883此地址为EMQX提供的敞开的地址,咱们在刚开始接触MQTT时可用此地址体验和调试,而且此地址没有设置用户名和暗码选项。

然后咱们调用mqttManager.connect(this)办法之后,就能够在Log中看到onSuccess的打印信息,代表着咱们现已衔接上EMQX。

订阅撤销订阅主题

了解了怎么衔接MQTT之后,咱们再来看看怎么去订阅和撤销订阅MQTT的主题,这一方面仍是比较简略的,订阅主题便是为了让客户端去接纳特定信息,假如客户端不去订阅主题那么就不会收到音讯,相反假如客户端订阅了一切的主题那么在处理音讯时也会遇到信息过大的问题。

// 订阅主题
fun subscribeTopic(topic: String) {
    mqttClient.subscribe(topic, qos = 0)
}
// 撤销订阅主题
fun unSubscribeTopic(topic: String) {
    mqttClient.unsubscribe(topic)
}
// 订阅chat/person/receiver/1
mqttClient.subscribe("chat/person/receiver/1", 0)

订阅主题和撤销订阅操作都是十分简略的,只需求将主题字符串传入给MqttAndroidClient即可,在订阅的办法中除了topic主题参数外,还有一个qos参数也是必传的,qos是一个十分关键的参数,它决定了客户端在订阅音讯时是否确保必定能够收到。qos的解说咱们放在发布和接纳音讯一同,发布和接纳音讯的部分也涉及到qos的常识,放在一同更简略理解它的效果。下面咱们先看下主题一般的格局是什么样的。

MQTT的主题类似于URL途径,运用反斜杠 / 来分层,比方一个谈天体系,咱们能够自行发布音讯给某人,也能够接纳其它人发给自己的音讯,而且也能够接纳群聊的音讯,下面咱们就能够自己界说一套主题来满足上述的要求。

  • 界说发送音讯给老友的主题:chat/person/send/{老友的id}
  • 界说接纳老友音讯的主题:chat/person/receiver/{老友的id}
  • 界说发送给某个群聊的音讯:chat/room/send/{群聊id}
  • 界说接纳某个群聊的音讯:chat/room/receiver/{群聊id}

这样界说好的主题分层就很明晰,四个层次别离为chat代表谈天;person和room代表谈天的类型;send和receiver代表发送和接纳类型;最后的id表明用户id和群聊的id。在topic中还能够运用通配符来大范围订阅音讯的效果,通常有单层通配符+和多层通配符#,单层通配符表明能够接纳此层级一切的音讯,比方chat/person/receiver/+此主题就能够chat/person/receiver/下一切id的音讯,不限制某个老友id,多层通配符表明能够接纳多个层级下一切的音讯,比方chat/#表明一切的谈天信息都能够接纳到,不限制单人仍是群聊的音讯。

限制符在客户端开发中需求慎重运用,假如限制的范围过大可能造成音讯过多的接纳,导致处理音讯时压力过大。

发布和承受音讯

在订阅部分咱们订阅了一个chat/person/receiver/1主题,下面咱们尝试给此主题发送一个音讯,看看是否能够顺畅的接纳到此音讯。

mqttManager.publishMsg("chat/person/receiver/1", "Hello MQTT")
fun publishMsg(topic: String, message: String) {
    mqttClient.publish(topic, message.toByteArray(), 0, false)
}

当咱们调用pulishMsg()办法之后,咱们在MqttCall.messageArrived()回调办法中就会接纳到此音讯。

发送音讯可直接调用AndroidMqttClient.publish()办法进行操作,此办法总共有四个参数,别离为:

  • topic:主题参数,在这儿咱们传入的为chat/person/receiver/1
  • payload:音讯详细内容参数,此参数为字节数组
  • qos:服务质量参数,在上面订阅主题的地方也提到了,下面会详细介绍它的效果
  • retained:字面意思为保存,它表明发送了此音讯之后,服务器是否保存此音讯,以便客户端在每次衔接后并订阅对应主题时都会立即收到此主题下的最新一条音讯,默以为false

qos全称Quality of Service(服务质量),它是一个int值,它总共有三种类型,别离为0,1和2

  • qos=0:最多送达一次,音讯有可能在传输进程中丢掉;
  • qos=1:至少送达一次,音讯至少确保送达到承受者一次,可能会送达屡次;
  • qos=2:恰好送达一次,音讯只会送达一次到承受方,不会呈现屡次或许0次的状况,此qos最为杂乱,涉及发送者和承受者之间屡次交互。
qos为0

qos为0时,音讯即发即弃,发送方发送完音讯之后,它不会等候承认也不会存储和重传,在此场景下一旦接纳方网络不稳定就会呈现音讯未接纳成功的状况。

详细传输进程如下图所示:

站在Android开发者的角度认识MQTT - 使用篇

qos为1

qos为1时,音讯的传输进程中增加了应答和重传机制,发送方在发送音讯之后,必须等候接纳方回应ACK,此刻才会以为音讯发送成功,在发送成功之前,发送方会存储此音讯便利下次重传。

详细传输进程如下图所示:

站在Android开发者的角度认识MQTT - 使用篇

此传输进程可能会造成重复发送音讯,比方在发送方发送了音讯A之后,接纳方由于网络延时等状况的确没能及时接纳到音讯A,导致了无法回复音讯ACK,在抵达ACK超时时刻之后,发送方会再次发送音讯A,此刻接纳方网络推迟康复之后会收到两条相同id的音讯,它就会履行ACK的音讯回复,这样就导致接纳方重复接纳音讯的问题。

qos为2

qos为2时,为了解决音讯丢掉和重复发送的问题,加强了音讯的承认流程,同时也增加了开支,每一次发送音讯发送方和接纳方都会履行两次恳求和回应流程,仍是以流程图的办法展现下详细传输进程:

站在Android开发者的角度认识MQTT - 使用篇

此流程总共分为四部分:

  • 首先发送发发送一条QOS为2的PUBLISH音讯,并存储此音讯,等候接纳方回应PUBREC音讯;
  • 当接纳方接纳到PUBLISH音讯后,会回应一条PUBREC音讯,此刻发送方接纳到PUBREC音讯时,会将存储的PUBLISH音讯删去,然后会履行发送PUBREL音讯,并将此音讯存储下来,以便后续运用此音讯重传,此刻重传就不会运用PUBLISH音讯了;
  • 当接纳方接纳到PUBREL音讯之后,就承认发送方不会再传递PUBLISH音讯了,它就会回复PUBCOMP音讯;
  • 最终在发送发接纳到PUBCOMP音讯时,就承认QOS为2的音讯发送成功。

此流程相关于QOS为0和1的流程都杂乱许多,在咱们项目傍边关于一些重要的动作指令能够运用此QOS,而关于一些心跳音讯则能够挑选重量级轻一些的0和1。

断开MQTT

通过文章上面的内容介绍之后,MQTT的运用基本上现已掌握了,还剩下一个断开衔接的操作,其实断开衔接也是十分简略的,只需求通过MqttAndroidClient.disconnect()办法即可

fun disConnect(){
    mqttClient.disconnect()
    mqttClient.unregisterResources()
}

假如想更详细的感知断开的成果,也能够运用disconnect(Object userContext, IMqttActionListener callback)办法,callback会回调断开成功和失利的成果。除了调用disconnect()办法之外,别忘记unregisterResources()一下,此办法内部会解注册内部的播送和Service。

上面便是Android运用MQTT的最简略的一些内容,后面还打算将TSL和SSL认证、MQTT源码分析的一些内容完善到此系列中,一方面协助自己更全面的了解MQTT的运行机制,另一方面也能够让咱们更深化的知道下MQTT。到这停止本篇就算完毕了,谢谢咱们的阅览。

MQTT系列文章:

站在Android开发者的视点知道MQTT – 运用篇

站在Android开发者的视点知道MQTT – TLS 认证篇

站在Android开发者的视点知道MQTT – 源码篇


关于我

我是Taonce,假如觉得本文对你有所协助,帮忙关注、赞或许收藏三连一下,谢谢~