Android MQTT开发之 Hivemq MQTT Client

使用一个开源库:hivemq-mqtt-client,这是Java生态的一个MQTT客户端框架,需要Java 8,Android上使用的话问题不大,需要一些额外的配置,下面列出了相关的配置,尤其是 packagingOptions,不然编译不过,因为框架使用了Java8新增的语言特性,所以 minSdk 设置为24,即Android7.0,如果要兼容Android7.0以下系统,可以参考这份详细文档配置一下语法脱糖的SDK: Installation on Android

android {
    defaultConfig {
        minSdk 24
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_8
        targetCompatibility JavaVersion.VERSION_8
    }
    kotlinOptions {
        jvmTarget = '8'
    }
    packagingOptions {
        resources {
            excludes += ['META-INF/INDEX.LIST', 'META-INF/io.netty.versions.properties']
        }
    }
}

dependencies {
    implementation 'com.hivemq:hivemq-mqtt-client:1.3.3'
}

刚开始在自动连接这块花了好多时间,最后才发现是设置用户名和密码的地方不对,一定要在设置自动重连(初始化Client)的地方设置,而不是连接的时候!下面是一个简单的使用示例代码

MqttManager.kt

import android.util.Log
import com.hivemq.client.mqtt.datatypes.MqttQos
import com.hivemq.client.mqtt.lifecycle.MqttClientConnectedContext
import com.hivemq.client.mqtt.lifecycle.MqttClientConnectedListener
import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedContext
import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedListener
import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient
import com.hivemq.client.mqtt.mqtt5.Mqtt5Client
import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode
import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish
import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAck
import java.util.UUID
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.function.Consumer

open class MqttListener {
    open fun onConnected() {}
    open fun onDisconnected() {}
    open fun onSubscribed(vararg topics: String) {}
    open fun onReceiveMessage(topic: String, data: ByteArray) {}
    open fun onSendMessage(topic: String, data: ByteArray) {}
}

/*
文档
https://github.com/hivemq/hivemq-mqtt-client
https://hivemq.github.io/hivemq-mqtt-client/docs/installation/android/
*/
class MqttManager private constructor() : MqttClientConnectedListener, MqttClientDisconnectedListener {
    private val executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) {
        Thread(it).apply { isDaemon = true }
    }

    private val mqttAsynClient: Mqtt5AsyncClient = Mqtt5Client.builder()
        .identifier(UUID.randomUUID().toString())
        .serverHost(SERVER_HOST)
        .serverPort(SERVER_PORT)
        .addConnectedListener(this)
        .addDisconnectedListener(this)
        .simpleAuth()//在初始化的时候设置账号密码,重连才能成功
        .username(USERNAME)
        .password(PASSWORD.toByteArray())
        .applySimpleAuth()
        .automaticReconnectWithDefaultConfig()//自动重连
        .buildAsync()

    private val listeners = mutableListOf<MqttListener>()

    private val subTopics
        get() = arrayOf("top1", "top2", "top3")

    fun addMqttListener(listener: MqttListener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener)
        }
    }

    fun removeMqttListener(listener: MqttListener) {
        listeners.remove(listener)
    }

    override fun onConnected(context: MqttClientConnectedContext) {
        Log.i(TAG, "onConnected()")
        for (l in listeners) {
            l.onConnected()
        }
        subscribeAll()
    }

    private fun subscribeAll() {
        CompletableFuture.supplyAsync({
            val futures = subTopics.map(::subscribe)
                .map {
                    it.thenCompose {
                        CompletableFuture.supplyAsync({
                            val success = !it.reasonString.isPresent
                            if (success) {
                                Log.i(TAG, "subscribe success")
                            } else {
                                Log.e(
                                    TAG, "subscribe() - reasonCodes=[${it.reasonCodes.joinToString(", ")}]" +
                                            ", reasonString=${it.reasonString}"
                                )
                            }
                            success
                        }, executor)
                    }
                }
                .toTypedArray()
            CompletableFuture.allOf(*futures).join()//等待所有订阅结果
            if(futures.all { it.get() }) {
                Log.i(TAG, "subscribeAll() - 全部订阅成功")
            }
            for (l in listeners) {
                l.onSubscribed(*subTopics)
            }
        }, executor)
    }

    override fun onDisconnected(context: MqttClientDisconnectedContext) {
        Log.e(
            TAG, "onDisconnected() - isConnected=${mqttAsynClient.state.isConnected}" +
                    ", isConnectedOrReconnect=${mqttAsynClient.state.isConnectedOrReconnect}"
        )
        for (l in listeners) {
            l.onDisconnected()
        }
    }

    fun connect() {
        mqttAsynClient
            .connectWith()
            .cleanStart(true)
            .keepAlive(30)
            .send()
            .thenAccept {
                if (it.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS) {
                    Log.i(TAG, "connect() - SUCCESS")
                } else {
                    Log.e(TAG, "connect() - ${it.reasonCode}")
                }
            }
    }

    fun disconnect() {
        mqttAsynClient.disconnect().thenAccept {
            Log.i(TAG, "disconnect()")
        }
    }


    private val callback = Consumer<Mqtt5Publish> {
        val topic = it.topic.toString()
        val data = it.payloadAsBytes
        processReceivedMessage(topic, data)
    }

    private fun processReceivedMessage(topic: String, data: ByteArray) {
        //处理接收的数据
        for (l in listeners) {
            l.onReceiveMessage(topic, data)
        }
    }

    fun subscribe(topic: String): CompletableFuture<Mqtt5SubAck> {
        return mqttAsynClient.subscribeWith()
            .topicFilter(topic)
            .noLocal(true)// we do not want to receive our own message
            .qos(MqttQos.AT_MOST_ONCE)
            .callback(callback)
            .executor(executor)
            .send()
    }

    fun unsubscribe(topic: String) {
        mqttAsynClient.unsubscribeWith()
            .topicFilter(topic)
            .send().thenAccept {
                Log.i(TAG, "unsubscribe() - $it")
            }
    }

    /**
     * 发送数据
     */
    fun publish(topic: String, payload: ByteArray) {
        mqttAsynClient.publishWith()
            .topic(topic)
            .qos(MqttQos.AT_MOST_ONCE)
            .payload(payload)
            .send()
            .thenAccept { mqtt5PublishResult ->
                mqtt5PublishResult.publish.let { mqtt5Publish ->
//                    val topic = mqtt5Publish.topic.toString()
                    val data = mqtt5Publish.payloadAsBytes
                    for (l in listeners) {
                        l.onSendMessage(topic, data)
                    }
                }
            }
    }

    companion object {
        private const val TAG = "MqttManager"

        private const val SERVER_HOST = "example.com"
        private const val SERVER_PORT = 1883 // 1883即TCP协议,host不要再加上"tcp://",否则连不成功
        private const val USERNAME = "admin"
        private const val PASSWORD = "123456"

        val instance = MqttManager()
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嵌入式MQTT客户端开发是一种在嵌入式系统中实现MQTT通信协议的开发过程。MQTT是一种轻量级的发布/订阅消息传输协议,常用于物联网设备间的通信。 要开发嵌入式MQTT客户端,你可以按照以下步骤进行: 1. 确定硬件平台:选择适合你的嵌入式系统的硬件平台,例如Arduino、Raspberry Pi等。 2. 确定开发语言:选择一种适合你的硬件平台的编程语言,例如C、C++等。 3. 导入MQTT库:根据你选择的开发语言和硬件平台,找到相应的MQTT库,并将其导入到你的开发环境中。 4. 配置连接参数:根据你的MQTT服务器的地址、端口以及其他连接参数,配置你的嵌入式设备与MQTT服务器之间的连接。 5. 实现订阅和发布功能:使用MQTT库提供的函数,实现订阅和发布功能。订阅功能允许你接收特定主题下的消息,而发布功能允许你向特定主题发送消息。 6. 处理接收到的消息:在接收到消息时,你可以编写相应的逻辑来处理这些消息。例如,解析消息内容、更新设备状态等。 7. 错误处理和调试:添加适当的错误处理机制和调试功能,以便在开发过程中能够及时发现和解决问题。 8. 测试和部署:对你的嵌入式MQTT客户端进行测试,并将其部署到你的嵌入式系统中。 以上是一般的嵌入式MQTT客户端开发步骤,具体的实现方式和步骤可能会根据你选择的硬件平台和开发语言而有所不同。希望对你有所帮助!如果你有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值