Android實(shí)現(xiàn)MQTT客戶端

目錄

MQTT簡(jiǎn)介

MQTT是機(jī)器對(duì)機(jī)器(M2M)/物聯(lián)網(wǎng)(IoT)連接協(xié)議。它被設(shè)計(jì)為一個(gè)極其輕量級(jí)的發(fā)布/訂閱消息傳輸協(xié)議。對(duì)于需要較小代碼占用空間和/或網(wǎng)絡(luò)帶寬非常寶貴的遠(yuǎn)程連接非常有用绊谭,是專為受限設(shè)備和低帶寬、高延遲或不可靠的網(wǎng)絡(luò)而設(shè)計(jì)尉辑。這些原則也使該協(xié)議成為新興的“機(jī)器到機(jī)器”(M2M)或物聯(lián)網(wǎng)(IoT)世界的連接設(shè)備积瞒,以及帶寬和電池功率非常高的移動(dòng)應(yīng)用的理想選擇。例如丐一,它已被用于通過衛(wèi)星鏈路與代理通信的傳感器藻糖、與醫(yī)療服務(wù)提供者的撥號(hào)連接,以及一系列家庭自動(dòng)化和小型設(shè)備場(chǎng)景库车。它也是移動(dòng)應(yīng)用的理想選擇巨柒,因?yàn)樗w積小,功耗低,數(shù)據(jù)包最小洋满,并且可以有效地將信息分配給一個(gè)或多個(gè)接收器晶乔。

效果演示

這是我連接的MQTT中文網(wǎng)的公共服務(wù)器

基礎(chǔ)知識(shí)

這里開發(fā)客戶端需要的知識(shí)不多,paho的核心庫(kù)都封裝好了芦岂,我們只需要了解下基礎(chǔ)的知識(shí)就行了瘪弓。

1.連接

這里我使用的MQTT3.1.1協(xié)議垫蛆,服務(wù)器地址是以tcp開頭的末尾加上端口號(hào)1883

val server = "tcp://mqtt.p2hp.com:1883" //服務(wù)端地址

其他的一些參數(shù)如下:
clientId:(作為客戶端的標(biāo)識(shí))禽最,這里我使用的是AndroidID,獲取不到的話就使用生成的一個(gè)UUID
CleanSession:設(shè)置不持久化的話袱饭,每次都是一次新會(huì)話
keepAliveInterval:發(fā)送心跳包的時(shí)間

 val androidId = DeviceUtils.getAndroidID()
        val clientId = if(!TextUtils.isEmpty(androidId)){
            androidId
        }else{
            UUID.randomUUID().toString()
        }
        mqttClient = MqttAndroidClient(context,serverUrl,clientId)
        connectOptions = MqttConnectOptions().apply {
            isCleanSession = false //是否會(huì)話持久化
            connectionTimeout = 30 //連接超時(shí)時(shí)間
            keepAliveInterval = 10 //發(fā)送心跳時(shí)間
            userName = name //如果設(shè)置了認(rèn)證川无,填的用戶名
            password = pass.toCharArray() //用戶密碼
        }
2.訂閱和發(fā)布


(1) 訂閱
這里的概念就好像你微博關(guān)注了一個(gè)博主,然后當(dāng)博主發(fā)布新的動(dòng)態(tài)虑乖,你這就可以收到懦趋,而這里的訂閱就是類似關(guān)注,訂閱的主題格式跟文件路徑差不多疹味,比如訂閱一個(gè)topic/1仅叫,當(dāng)然這里也有帶通配符的訂閱方式比如topic/##的意思就是匹配所有糙捺,也就是當(dāng)你訂閱了topic/#的主題你就可以收到所有topic開頭的主題消息诫咱,像topic/1topic/2洪灯、topic/3等坎缭。
(2) 發(fā)布
發(fā)布消息的時(shí)候也需要指定一個(gè)主題,比如topic/1签钩,但是不能指定帶通配符的主題掏呼。
(3) QOS
另外還有一個(gè)比較重要的概念就是QOS(服務(wù)質(zhì)量),這里有3個(gè)值(0,1,2)铅檩,代表的意義如下:
0:代表憎夷,Sender 發(fā)送的一條消息,Receiver 最多能收到一次昧旨,也就是說(shuō) Sender 盡力向 Receiver 發(fā)送消息岭接,如果發(fā)送失敗,也就算了臼予。
1:代表鸣戴,Sender 發(fā)送的一條消息,Receiver 至少能收到一次粘拾,也就是說(shuō) Sender 向 Receiver 發(fā)送消息窄锅,如果發(fā)送失敗,會(huì)繼續(xù)重試,直到 Receiver 收到消息為止入偷,但是因?yàn)橹貍鞯脑蜃仿浚琑eceiver 有可能會(huì)收到重復(fù)的消息。
2:代表疏之,Sender 發(fā)送的一條消息殿雪,Receiver 確保能收到而且只收到一次,也就是說(shuō) Sender 盡力向 Receiver 發(fā)送消息锋爪,如果發(fā)送失敗丙曙,會(huì)繼續(xù)重試,直到 Receiver 收到消息為止其骄,同時(shí)保證 Receiver 不會(huì)因?yàn)橄⒅貍鞫盏街貜?fù)的消息亏镰。

實(shí)現(xiàn)步驟

1.引入依賴

這里本來(lái)我是用的這個(gè)庫(kù)https://github.com/eclipse/paho.mqtt.android,但是這個(gè)庫(kù)不適配Android12因此我下載了源碼調(diào)整了下拯爽,重新自己封裝了一個(gè)索抓,如下:

allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

dependencies {
            implementation 'com.github.itfitness:MQTTAndroid:1.0.0'
    }
2.封裝方法

我對(duì)這個(gè)庫(kù)進(jìn)行了一些封裝,如下:

class MQTTHelper{
    private val mqttClient: MqttAndroidClient
    private val connectOptions: MqttConnectOptions
    private var mqttActionListener: IMqttActionListener? = null
    constructor(context: Context, serverUrl:String, name:String, pass:String){
        val macAddress = DeviceUtils.getAndroidID()
        val clientId = if(!TextUtils.isEmpty(macAddress)){
            macAddress
        }else{
            UUID.randomUUID().toString()
        }
        mqttClient = MqttAndroidClient(context,serverUrl,clientId)
        connectOptions = MqttConnectOptions().apply {
            isCleanSession = false
            connectionTimeout = 30
            keepAliveInterval = 10
            userName = name
            password = pass.toCharArray()
        }
    }

    /**
     * 連接
     * @param mqttCallback 接到訂閱的消息的回調(diào)
     * @param isFailRetry 失敗是否重新連接
     */
    fun connect(topic: Topic, qos: Qos, isFailRetry:Boolean, mqttCallback: MqttCallback){
        mqttClient.setCallback(mqttCallback)
        if(mqttActionListener == null){
            mqttActionListener = object :IMqttActionListener{
                override fun onSuccess(asyncActionToken: IMqttToken?) {
                    LogUtils.eTag("連接","連接成功")
                    subscribe(topic,qos)
                }
                override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                    //失敗重連
                    LogUtils.eTag("連接","連接失敗重試${exception?.message}")
                    if (isFailRetry){
                        mqttClient.connect(connectOptions,null,mqttActionListener)
                    }
                }
            }
        }
        mqttClient.connect(connectOptions,null,mqttActionListener)
    }

    /**
     * 訂閱
     */
    private fun subscribe(topic: Topic,qos:Qos){
        mqttClient.subscribe(topic.value(),qos.value())
    }

    /**
     * 發(fā)布
     */
    fun publish(topic:Topic,message:String,qos:Qos){
        val msg = MqttMessage()
        msg.isRetained = false
        msg.payload = message.toByteArray()
        msg.qos = qos.value()
        mqttClient.publish(topic.value(),msg)
    }

    /**
     * 斷開連接
     */
    fun disconnect(){
        mqttClient.disconnect()
    }
}
enum class Qos{
    QOS_ZERO{
        override fun value():Int{
            return 0
        }
    },
    QOS_ONE{
        override fun value():Int{
            return 1
        }
    },
    QOS_TWO{
        override fun value():Int{
            return 2
        }
    };
    abstract fun value(): Int
}
enum class Topic{
    //訂閱主題
    TOPIC_MSG{
        override fun value():String{
            return "testtopic/#"
        }
    },
    //發(fā)布主題
    TOPIC_SEND{
        override fun value():String{
            return "testtopic/1"
        }
    };
    abstract fun value(): String
}
3.使用

在Activity中的使用如下:

class MainActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.N)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val server = "tcp://mqtt.p2hp.com:1883" //服務(wù)端地址
        val mqttHelper = MQTTHelper(this,server,"123","123")
        mqttHelper.connect(Topic.TOPIC_MSG, Qos.QOS_TWO,false,object : MqttCallback {
            override fun connectionLost(cause: Throwable?) {

            }

            override fun messageArrived(topic: String?, message: MqttMessage?) {
                //收到消息
                message?.payload?.let { ToastUtils.showShort(String(it)) }
                LogUtils.eTag("消息", message?.payload?.let { String(it) })
            }

            override fun deliveryComplete(token: IMqttDeliveryToken?) {



            }
        })
        val etMsg = findViewById<EditText>(R.id.et_msg)
        findViewById<Button>(R.id.tv_send).setOnClickListener {
            //發(fā)送消息
            mqttHelper.publish(Topic.TOPIC_SEND,etMsg.text.toString(),Qos.QOS_TWO)
        }
    }
}

案例源碼

https://github.com/itfitness/MQTTAndroid

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毯炮,一起剝皮案震驚了整個(gè)濱河市逼肯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桃煎,老刑警劉巖篮幢,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異备禀,居然都是意外死亡洲拇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門曲尸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赋续,“玉大人,你說(shuō)我怎么就攤上這事另患∨β遥” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵昆箕,是天一觀的道長(zhǎng)鸦列。 經(jīng)常有香客問我,道長(zhǎng)鹏倘,這世上最難降的妖魔是什么薯嗤? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮纤泵,結(jié)果婚禮上骆姐,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好玻褪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布肉渴。 她就那樣靜靜地躺著,像睡著了一般带射。 火紅的嫁衣襯著肌膚如雪同规。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天窟社,我揣著相機(jī)與錄音券勺,去河邊找鬼。 笑死桥爽,一個(gè)胖子當(dāng)著我的面吹牛朱灿,可吹牛的內(nèi)容都是我干的昧识。 我是一名探鬼主播钠四,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跪楞!你這毒婦竟也來(lái)了缀去?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤甸祭,失蹤者是張志新(化名)和其女友劉穎缕碎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體池户,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咏雌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了校焦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赊抖。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖寨典,靈堂內(nèi)的尸體忽然破棺而出氛雪,到底是詐尸還是另有隱情,我是刑警寧澤耸成,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布报亩,位于F島的核電站,受9級(jí)特大地震影響井氢,放射性物質(zhì)發(fā)生泄漏弦追。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一花竞、第九天 我趴在偏房一處隱蔽的房頂上張望劲件。 院中可真熱鬧,春花似錦、人聲如沸寇仓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遍烦。三九已至俭嘁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間服猪,已是汗流浹背供填。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留罢猪,地道東北人近她。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像膳帕,于是被迫代替她去往敵國(guó)和親粘捎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容