🍺

【Android】AndoidでMQTT

2024/07/06に公開

はじめに

Androidでmqttを使ったメッセージの送受信を実装したので、その覚書き
実装にはEclipse Pahoを使用した
開発環境はAndroidStudio Flamingo

実装

まずはbuild.gradleに下記を追記して、依存関係を追加していく

dependencies {
    implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
    implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
}

AndroidManifestはネットワーク関連の権限とeclipse.pahoのMqttサービスを追加しておく

<application
        <service android:name="org.eclipse.paho.android.service.MqttService" />
    </application>

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

kotlinで実際にクライアントを作って、MQTTをPublish/Subscribeする部分を書いていく
コード全体は最後に記載する

serverURIの部分は、今回はローカルのmosquittoブローカーと接続する必要があったので、接続先のアドレスは10.0.2.2としている
※AndroidStudioなどの仮想端末から見た場合、ホストのPC端末(仮想端末のホストPC)はアドレス10.0.2.2となる。localhostだとつながらないようなので注意する。

トピックはtestの部分を変えれば良い

private val serverURI = "tcp://10.0.2.2:1883"
private val topic = "test"

コードをそのまま実行するとsubscribeとpublishの両方とも動いてしまうので、注意
必要に応じてコメントアウトなどすること
publish時のメッセージは"hello android"の部分を変えるだけ

subscribeToTopic(topic)
publishMessage(topic, "hello android")

コード全体

class MainActivity : AppCompatActivity() {
    val TAG = "Android-Mqtt"
    private lateinit var mqttClient: MqttAndroidClient
    private val serverURI = "tcp://10.0.2.2:1883"
    private val topic = "test"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mqttClient = MqttAndroidClient(this, serverURI, "kotlin_client")

        mqttClient.setCallback(object : MqttCallback {
            override fun messageArrived(topic: String?, message: MqttMessage?) {
                Log.d(TAG, "Receive message: ${message.toString()} from topic: $topic")
            }

            override fun connectionLost(cause: Throwable?) {
                Log.d(TAG, "Connection lost ${cause.toString()}")
            }

            override fun deliveryComplete(token: IMqttDeliveryToken?) {
                Log.d(TAG, "Delivery complete")
            }
        })

        connectAndOperateMQTT()
    }

    private fun connectAndOperateMQTT() {
        val options = MqttConnectOptions().apply {
            isCleanSession = true
            connectionTimeout = 10
            keepAliveInterval = 20
        }

        try {
            mqttClient.connect(options, null, object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken?) {
                    Log.d(TAG, "Connection success")
                    subscribeToTopic(topic)
                    publishMessage(topic, "hello android")
                }

                override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                    Log.d(TAG, "Connection failure", exception)
                }
            })
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    private fun subscribeToTopic(topic: String) {
        try {
            mqttClient.subscribe(topic, 1, null, object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken?) {
                    Log.d(TAG, "Subscribed to topic $topic")
                }

                override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                    Log.d(TAG, "Failed to subscribe to topic $topic", exception)
                }
            })
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    private fun publishMessage(topic: String, message: String) {
        try {
            val mqttMessage = MqttMessage()
            mqttMessage.payload = message.toByteArray()
            mqttClient.publish(topic, mqttMessage)
            Log.d(TAG, "Message published to topic $topic")
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    private fun disconnect() {
        try {
            mqttClient.disconnect(null, object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken?) {
                    Log.d(TAG, "Disconnected successfully")
                }

                override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                    Log.d(TAG, "Failed to disconnect", exception)
                }
            })
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (mqttClient.isConnected) {
            disconnect()
        }
    }
}

所感・注意点

注意点
AndroidのAPIレベルや、使用している環境によっては下記のようなエラーが出る

Failed resolution of: Landroid/support/v4/content/LocalBroadcastManager;

調べると、どうやらEclipse PahoがAndroidの古いサポートライブラリ?を参照しているようで
gradle.propertiesに下記の追記が必要だった

android.enableJetifier=true
android.useAndroidX=true

下記のサイトを含め、いろいろ参考に実装していったが、意外と手間取った。
自分はあまりAndroidアプリ開発に慣れていないので、エラーへの対処法がわからず勉強不足を実感する。

参考サイト

https://qiita.com/emqx_japan/items/8ab4f41f8262a40560ef
https://qiita.com/yujimny/items/36699ec332c50a1526ea

Discussion