MQTT クライアント を Android に実装する

# 背景
IoTと言う言葉が流行るにつれて、MQTTという言葉も注目されている。

来月、おきなわマラソンで、お友達との距離をリアルタイムに把握するシステムを開発しようと思っています。

双方向の通信と言えば、WebSocketでやればいいじゃんという話もありますが、MQTTの特徴であるRetain機能を使えば、アプリ起動時に、お友達の最新の位置情報をすぐ取得できるのではないか？ということで、Android端末でMQTTのクライアント実装を行ってみました。

一応、覚え書きです。

# 環境

MQTTのサーバとして、時雨堂さんが提供している[sango](https://sango.shiguredo.jp/)のライトプラン
開発環境は、Android Studio 1.5.1 (OSX版）

Android端末は
Google Nexus 5X (Android 6.0.1)
Sony Xperia Z3 (Android 5.0.2) 

# 必要なライブラリ

Android MQTTで検索してみたところ、[Paho Android Service](https://eclipse.org/paho/clients/android/) が良さそうだったので、これを利用しました。

私は、下記のバイナリファイル

org.eclipse.paho.client.mqttv3-1.0.2.jar
org.eclipse.paho.android.service-1.0.2.jar

をapp/libs フォルダに放りこみました。

このまま、コンパイルすると、リンク時にエラーが発生するので、

```lang:build.gradle
android {

... 省略

    packagingOptions {
        exclude 'META-INF/ECLIPSE_.SF'
        exclude 'META-INF/ECLIPSE_.RSA'
    }
}
```

を追記することで、apkファイルがビルド出来ました。

![160119-0001.png](https://qiita-image-store.s3.amazonaws.com/0/33220/9673f050-b8cb-37a6-ba7b-97ad0b87fd1d.png)

# Android Manifestの設定

パーミションとサービスの追記が必要になります

```lang:AndroidManifest.xml
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

... 省略 

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

# コード例 (subscribe)

```java:MainActivity.java
public class MainActivity extends AppCompatActivity {

    private final String TAG = "Debug";
    private MqttAndroidClient mqttAndroidClient;

    private String ID = "hoge@github";
    private String PASS = "sango";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mqttAndroidClient = new MqttAndroidClient(this, "tcp://lite.mqtt.shiguredo.jp:1883", "d:lite:test:") {
            @Override
            public void onReceive(Context context, Intent intent) {
                super.onReceive(context, intent);

                Bundle data = intent.getExtras();

                String action = data.getString("MqttService.callbackAction");
                Object parcel = data.get("MqttService.PARCEL");
                String destinationName = data.getString("MqttService.destinationName");

                if(action.equals("messageArrived"))
                {
                    Log.d(TAG,destinationName + " " + parcel.toString());
                }

            }
        };

        try {
            MqttConnectOptions options = new MqttConnectOptions();
            options.setUserName(ID);
            options.setPassword(PASS.toCharArray());

            mqttAndroidClient.connect(options, null, new IMqttActionListener() {
                @Override
                public void onSuccess(IMqttToken iMqttToken) {
                    Log.d(TAG, "onSuccess");

                    try {
                        mqttAndroidClient.subscribe("hoge@github/#", 0);
                        Log.d(TAG, "subscribe");
                    } catch (MqttException e) {
                        Log.d(TAG, e.toString());
                    }
                }

                @Override
                public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
                    Log.d(TAG, "onFailure");
                }
            });

        } catch (MqttException e) {
            Log.d(TAG, e.toString());
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        try {

            if(mqttAndroidClient.isConnected()) {
                mqttAndroidClient.disconnect();
                Log.d(TAG,"disconnect");
            }

            mqttAndroidClient.unregisterResources();

        } catch (MqttException e) {
            Log.d(TAG,e.toString());
        }

    }
}
```

# コード例 (publish)

```java:MainActivity.java
	btn01.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(mqttAndroidClient == null)
                    return;

                try {
                    if(mqttAndroidClient.isConnected()) {
                        IMqttDeliveryToken token = mqttAndroidClient.publish("hoge@github/test", "hello world".getBytes(), 0, true);
                    }
                } catch (MqttPersistenceException e) {
                    Log.d(TAG,e.toString());
                } catch (MqttException e) {
                    Log.d(TAG,e.toString());
                }
            }
        });
```


# 動作検証用 (node.jsコード)

これは必要か？わかりませんが、開発中にちょっとしたnode.js向けのコードも書いたので、これも参考までに

共通設定ファイル

```json:config.json
{
  "host": "lite.mqtt.shiguredo.jp",
  "port": 1883,
  "options": {
    "username": "hoge@github",
    "password": "sango",
    "keepalive": 10000
  },
  "topic": "hoge@github"
}
```

subscribe用

```js:sub.js
var mqtt = require("mqtt"),
    config = require("./config.json"),
    client = mqtt.createClient(config.port, config.host, config.options);

client.subscribe(config.topic + "/#");

client.on("message", function(topic, message) {
  console.log(topic, message.toString());
});
```

publish用

```js:pub.js
var mqtt = require("mqtt"),
    config = require("./config.json"),
    client = mqtt.createClient(config.port, config.host, config.options);

setInterval(function() {
	client.publish(config.topic + "/test", "abc");
	}, 30000);

```

以上、ご参考までに