目录
MQTT介绍
MQTT协议中的订阅、主题、会话
一、订阅(Subscription)
二、会话(Session)
三、主题名(Topic Name)
四、主题筛选器(Topic Filter)
五、负载(Payload)
MQTT协议中的方法
MQTT服务质量 (QoS)
Android 具体实现
第一步,添加依赖
第二步,声明权限
第三步,开启服务:
第四步,具体实现
创建Service
回调接口:
实现一个ServiceConnection类
使用:
实现MQTT协议需要客户端和服务器端通讯完成。
在通讯过程中,MQTT协议中有三种身份:
发布者(Publish)
代理(Broker)(服务器)
订阅者(Subscribe)
消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:
Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内(payload);
payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。
当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关连。
订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。
消息订阅者所具体接收的内容。
qos为0:“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
qos为1:“至少一次”,确保消息到达,但消息重复可能会发生。这一级别可用于如下情况,你需要获得每一条消息,并且消息重复发送对你的使用场景无影响。
qos为2:“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
首先先要确认配置参数:
然后,配置android中的mqtt相关库。
在项目根目录下的build.gradle中添加:
repositories {
maven {
url "https://repo.eclipse.org/content/repositories/paho-releases/"
}
}
然后在app目录下的build.gradle中添加:
dependencies {
compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.1'
}
在AndroidManifest.xml中添加:
同样是在AndroidManifest.xml中添加:
配置结束。
=====================================================================
考虑一下项目中,mqtt作用是什么,其实就是通信、建立长连接。那么肯定是要放在服务里面的。
MQTT 的配置放入Service中去,所以需要创建一个Service。
class MQTTService : Service() {
private var conOpt: MqttConnectOptions? = null
private val host = "tcp://192.168.0.11:61613"
private val userName = "admin"
private val passWord = "password"
private val clientId = "androidId" //客户端标识
private var IGetMessageCallBack: IGetMessageCallBack? = null
override fun onCreate() {
super.onCreate()
Log.e(javaClass.name, "onCreate")
init()
}
private fun init() {
// 服务器地址(协议+地址+端口号)
val uri = host
MQTTService.Companion.client = MqttAndroidClient(this, uri, clientId)
// 设置MQTT监听并且接受消息
MQTTService.Companion.client.setCallback(mqttCallback)
conOpt = MqttConnectOptions()
// 清除缓存
conOpt.setCleanSession(true)
// 设置超时时间,单位:秒
conOpt.setConnectionTimeout(10)
// 心跳包发送间隔,单位:秒
conOpt.setKeepAliveInterval(20)
// 用户名
conOpt.setUserName(userName)
// 密码
conOpt.setPassword(passWord.toCharArray()) //将字符串转换为字符串数组
// last will message
var doConnect = true
val message = "{\"terminal_uid\":\"$clientId\"}"
Log.e(javaClass.name, "message是:$message")
val topic: String = MQTTService.Companion.myTopic
val qos = 0
val retained = false
if (message != "" || topic != "") {
// 最后的遗嘱
// MQTT本身就是为信号不稳定的网络设计的,所以难免一些客户端会无故的和Broker断开连接。
//当客户端连接到Broker时,可以指定LWT,Broker会定期检测客户端是否有异常。
//当客户端异常掉线时,Broker就往连接时指定的topic里推送当时指定的LWT消息。
try {
conOpt.setWill(topic, message.toByteArray(), qos, retained.toBoolean())
} catch (e: Exception) {
Log.i(MQTTService.Companion.TAG, "Exception Occured", e)
doConnect = false
iMqttActionListener.onFailure(null, e)
}
}
if (doConnect) {
doClientConnection()
}
}
override fun onDestroy() {
stopSelf()
try {
MQTTService.Companion.client.disconnect()
} catch (e: MqttException) {
e.printStackTrace()
}
super.onDestroy()
}
/** 连接MQTT服务器 */
private fun doClientConnection() {
if (!MQTTService.Companion.client.isConnected() && isConnectIsNormal) {
try {
MQTTService.Companion.client.connect(conOpt, null, iMqttActionListener)
} catch (e: MqttException) {
e.printStackTrace()
}
}
}
// MQTT是否连接成功
private val iMqttActionListener: IMqttActionListener = object : IMqttActionListener() {
fun onSuccess(arg0: IMqttToken?) {
Log.i(MQTTService.Companion.TAG, "连接成功 ")
try {
// 订阅myTopic话题
MQTTService.Companion.client.subscribe(MQTTService.Companion.myTopic, 1)
} catch (e: MqttException) {
e.printStackTrace()
}
}
fun onFailure(arg0: IMqttToken?, arg1: Throwable) {
arg1.printStackTrace()
// 连接失败,重连
}
}
// MQTT监听并且接受消息
private val mqttCallback: MqttCallback = object : MqttCallback() {
@Throws(Exception::class)
fun messageArrived(topic: String, message: MqttMessage) {
val str1: String = String(message.getPayload())
if (IGetMessageCallBack != null) {
IGetMessageCallBack.setMessage(str1)
}
val str2 = topic + ";qos:" + message.getQos() + ";retained:" + message.isRetained()
Log.i(MQTTService.Companion.TAG, "messageArrived:$str1")
Log.i(MQTTService.Companion.TAG, str2)
}
fun deliveryComplete(arg0: IMqttDeliveryToken?) {}
fun connectionLost(arg0: Throwable?) {
// 失去连接,重连
}
}
/** 判断网络是否连接 */
private val isConnectIsNormal: Boolean
private get() {
val connectivityManager: ConnectivityManager = this.applicationContext
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val info: NetworkInfo = connectivityManager.getActiveNetworkInfo()
return if (info != null && info.isAvailable()) {
val name: String = info.getTypeName()
Log.i(MQTTService.Companion.TAG, "MQTT当前网络名称:$name")
true
} else {
Log.i(MQTTService.Companion.TAG, "MQTT 没有可用网络")
false
}
}
override fun onBind(intent: Intent?): IBinder {
Log.e(javaClass.name, "onBind")
return CustomBinder()
}
fun setIGetMessageCallBack(IGetMessageCallBack: IGetMessageCallBack?) {
this.IGetMessageCallBack = IGetMessageCallBack
}
inner class CustomBinder : Binder() {
val service: MQTTService
get() = this@MQTTService
}
fun toCreateNotification(message: String?) {
val pendingIntent: PendingIntent = PendingIntent.getActivity(
this, 1, Intent(
this,
MQTTService::class.java
), PendingIntent.FLAG_UPDATE_CURRENT
)
val builder: NotificationCompat.Builder = Builder(this) //3、创建一个通知,属性太多,使用构造器模式
val notification: Notification = builder
.setTicker("测试标题")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("")
.setContentText(message)
.setContentInfo("")
.setContentIntent(pendingIntent) //点击后才触发的意图,“挂起的”意图
.setAutoCancel(true) //设置点击之后notification消失
.build()
val notificationManager: NotificationManager =
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
startForeground(0, notification)
notificationManager.notify(0, notification)
}
companion object {
val TAG = MQTTService::class.java.simpleName
private val client: MqttAndroidClient? = null
private const val myTopic = "ForTest" //要订阅的主题
fun publish(msg: String) {
val topic: String = MQTTService.Companion.myTopic
val qos = 0
val retained = false
try {
if (MQTTService.Companion.client != null) {
MQTTService.Companion.client.publish(
topic,
msg.toByteArray(),
qos,
retained.toBoolean()
)
}
} catch (e: MqttException) {
e.printStackTrace()
}
}
}
}
interface IGetMessageCallBack {
void setMessage(message:String )
}
看过的博客中都是通过startService去启动服务的,那样搞的话有点耦合。不太好。
这里是通过BindService去启动服务,所以没有onStartCommond方法。
同时,当获取从服务器推送过来的消息时,是使用回调去更新UI,这样做是为了方便代码的迁移。
关于 bindservice和startservice 区别 可以去看看Android Service 使用bindservice和startservice 区别
为了实现通过这个回调去传递从服务端获取到的消息,我们需要实现一个ServiceConnection类,并且通过onBind来从Service和Activity之间传递数据:
class MyServiceConnection : ServiceConnection {
var mqttService: MQTTService? = null
private set
private var IGetMessageCallBack: IGetMessageCallBack? = null
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
mqttService = (iBinder as MQTTService.CustomBinder).getService()
mqttService!!.setIGetMessageCallBack(IGetMessageCallBack)
}
override fun onServiceDisconnected(componentName: ComponentName) {}
fun setIGetMessageCallBack(IGetMessageCallBack: IGetMessageCallBack?) {
this.IGetMessageCallBack = IGetMessageCallBack
}
}
class MainActivity : AppCompatActivity(), IGetMessageCallBack {
private var textView: TextView? = null
private var button: Button? = null
private var serviceConnection: MyServiceConnection? = null
private var mqttService: MQTTService? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.text) as TextView
button = findViewById(R.id.button) as Button
serviceConnection = MyServiceConnection()
serviceConnection!!.setIGetMessageCallBack(this@MainActivity)
val intent = Intent(this, MQTTService::class.java)
bindService(intent, serviceConnection!!, Context.BIND_AUTO_CREATE)
button.setOnClickListener(object : OnClickListener() {
fun onClick(view: View?) {
MQTTService.publish("测试一下子")
}
})
}
fun setMessage(message: String?) {
textView!!.text = message
mqttService = serviceConnection!!.mqttService
mqttService!!.toCreateNotification(message)
}
override fun onDestroy() {
unbindService(serviceConnection!!)
super.onDestroy()
}
}
结束。