概述
上节完成在前台服务开启后,初始化连接MQTT。业已实现了Android 后台长连接服务器,随时能够响应嘟妈指令,建立音视频通信。MQTT初始化连接需要以下步骤:
- MqttClient 构造函数创建实例,设置连接地址与唯一ID
- 连接前通过实例设置设置回调函数(处理连接、消息事件)
- 实例调用connect,参数MqttConnectOptions,设置连接参数如心跳等
- 实例调用disconnect、close关闭连接
MQTT与后台服务器建立通信成功之后,可以正常通信,发送接收数据。
- 发布消息
- 订阅消息
- 在异常断开连接之后,医嘱消息
嘟宝通过MQTT发布消息
MQTT发送消息,是通过主题发送的。服务器则根据主题接收消息,服务器将消息转发给订阅主题的人。主题通过 / 来区分层级,类似于 URL 路径,例如:
/dubao
/dubao/id110
/dubao/+/id110
MQTT 主题支持以下两种通配符:+ 和 #。
+:表示单层通配符,例如 a/+ 匹配 a/x 或 a/y。
#:表示多层通配符,例如 a/# 匹配 a/x、a/b/c/d。
注意:通配符主题只能用于订阅,不能用于发布。
主题的详细说明参考
MqttClient 提供了以下发布消息的方法
publish(String topic, byte[] payload, int qos, boolean retained)// 简洁易用
publish(String topic, MqttMessage message)// 常用
topic:主题,类型为字符串 payload:实际要发送的数据 qos:消息发送质量,可设置0、1、2
- QoS 0 - 最多一次(即发即弃,不保证送达)
- QoS 1 - 至少一次(保证送达,可能重复)
- QoS 2 - 恰好一次(保证送达且不重复) retained:消息是否保留。订阅者每次连接MQTT,都会收到最新保留信息 MqttMessage 包含payload、qos、retained参数,增加 isDuplicate 、getId 等参数
通过MqttMessage 封装发布消息的函数
核心代码
public void publish(String topic,String msg){
publish(topic,msg,0,false);
}
public void publish(String topic,String msg,int qos,boolean retained){
mWorkHandler.post(()->{
try {
MqttMessage message=new MqttMessage(msg.getBytes(StandardCharsets.UTF_8));
message.setQos(qos);
message.setRetained(retained);
client.publish(topic,message);
} catch (MqttException e) {
e.printStackTrace();
}
});
}
MQTT发送实例
- 在MainActivity创建输入控件,发送控件
- 在MainActivity初始化mqtt连接使用上节设计的类
- 通过mqtt测试工具订阅主题接收消息
MainActivity源码
package com.zilong.dubao;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
MyMqttClient myMqttClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myMqttClient=new MyMqttClient();
myMqttClient.connect();
Button startbtn=findViewById(R.id.sendbtn);
startbtn.setOnClickListener(v->{
String topic=((EditText)findViewById(R.id.topic)).getText().toString();
String content=((EditText)findViewById(R.id.content)).getText().toString();
myMqttClient.publish(topic,content);
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:text="主题"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/topic"
android:text="/dubao"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:text="内容"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/content"
android:text="我是嘟宝....."
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/sendbtn"
android:text="发送"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
MyMqttClient类源码
package com.zilong.dubao;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.nio.charset.StandardCharsets;
public class MyMqttClient {
private Handler mWorkHandler;
private MqttClient client;
private MqttConnectOptions connOpts;
MyMqttClient(){
HandlerThread handlerThread = new HandlerThread("worker");
handlerThread.start();
mWorkHandler = new Handler(handlerThread.getLooper());
}
public void connect() {
mWorkHandler.post(()->{
_connect();
});
}
private MqttCallbackExtended callbackExtendedallback =new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
Log.d("mqtt","connectComplete");
publish("/dubao","嘟宝上线了");
}
@Override
public void connectionLost(Throwable cause) {
Log.d("mqtt","connectionLost");
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String s=new String(message.getPayload());
Log.d("mqtt","有消息来了");
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
}
};
protected void _connect(){
try {
String url= "tcp://"+MyConfig.mqttip+":"+MyConfig.mqttport;
String dubaoID="0001";
client=new MqttClient(url, "dubao_server"+dubaoID, new MemoryPersistence());
connOpts=new MqttConnectOptions();
connOpts.setCleanSession(true);// 重连接是否清理会话
connOpts.setConnectionTimeout(10);
connOpts.setKeepAliveInterval(60);//心跳间隔(秒)
connOpts.setAutomaticReconnect(true);
client.setCallback(callbackExtendedallback);
_connectionMQTTServer();
} catch (MqttException e) {
e.printStackTrace();
}
}
protected void _connectionMQTTServer(){
while (true){
try {
client.connect(connOpts);
break;
} catch (MqttException e) {
Log.d("mqtt","连接失败");
e.printStackTrace();
}
try {
Thread.sleep(5*1000);
Log.d("mqtt","准备重新连接");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void publish(String topic,String msg){
publish(topic,msg,0,false);
}
public void publish(String topic,String msg,int qos,boolean retained){
mWorkHandler.post(()->{
try {
MqttMessage message=new MqttMessage(msg.getBytes(StandardCharsets.UTF_8));
message.setQos(qos);
message.setRetained(retained);
client.publish(topic,message);
} catch (MqttException e) {
e.printStackTrace();
}
});
}
public void colse(){
mWorkHandler.post(()->{
try {
client.disconnect();
client.close();
} catch (MqttException e) {
e.printStackTrace();
}
});
}
}
运行查看实例效果
订阅消息
MQTT接口提供了两个最核心的订阅方法,
- 指定QoS订阅:最常用的方式,明确指定订阅时请求的最大服务质量。
- 默认QoS订阅:使用默认的QoS级别1进行订阅。
void subscribe(String topicFilter, int qos) throws MqttException;
void subscribe(String topicFilter) throws MqttException;
topicFilter订阅主题,qos消息发送质量,可设置0、1、2。
- 批量订阅多个主题
- 使用通配符订阅
+单层通配符。匹配该层级下的任意一个词,如定于/duma/+/1,匹配/duma/a/1,/duma/b/1等#多层通配符。匹配主题中它之后的所有层级,必须放在主题末尾。如#duma,可配备/duma/a,/duma/b/c等 QOS订阅质量,有发布、与订阅共同决定。
订阅消息实例
- MQTT连接成功后,订阅主题
- connectComplete回调函数订阅主题/duma/#
- messageArrived回调函数中处理接收信息 MyMqttClient代码
package com.zilong.dubao;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.Toast;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.nio.charset.StandardCharsets;
public class MyMqttClient {
private Handler mWorkHandler;
private MqttClient client;
private MqttConnectOptions connOpts;
MyMqttClient(){
HandlerThread handlerThread = new HandlerThread("worker");
handlerThread.start();
mWorkHandler = new Handler(handlerThread.getLooper());
}
public void connect() {
mWorkHandler.post(()->{
_connect();
});
}
private MqttCallbackExtended callbackExtendedallback =new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
Log.d("mqtt","connectComplete");
try {
client.subscribe("/duma/#");
} catch (MqttException e) {
e.printStackTrace();
}
}
@Override
public void connectionLost(Throwable cause) {
Log.d("mqtt","connectionLost");
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String s=new String(message.getPayload());
Log.d("mqtt","主题是:"+topic);
Log.d("mqtt","内容是::"+s);
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
}
};
protected void _connect(){
try {
String url= "tcp://"+MyConfig.mqttip+":"+MyConfig.mqttport;
String dubaoID="0001";
client=new MqttClient(url, "dubao_server"+dubaoID, new MemoryPersistence());
connOpts=new MqttConnectOptions();
connOpts.setCleanSession(true);// 重连接是否清理会话
connOpts.setConnectionTimeout(10);
connOpts.setKeepAliveInterval(60);//心跳间隔(秒)
connOpts.setAutomaticReconnect(true);
client.setCallback(callbackExtendedallback);
_connectionMQTTServer();
} catch (MqttException e) {
e.printStackTrace();
}
}
protected void _connectionMQTTServer(){
while (true){
try {
client.connect(connOpts);
break;
} catch (MqttException e) {
Log.d("mqtt","连接失败");
e.printStackTrace();
}
try {
Thread.sleep(5*1000);
Log.d("mqtt","准备重新连接");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void publish(String topic,String msg){
publish(topic,msg,0,false);
}
public void publish(String topic,String msg,int qos,boolean retained){
mWorkHandler.post(()->{
try {
MqttMessage message=new MqttMessage(msg.getBytes(StandardCharsets.UTF_8));
message.setQos(qos);
message.setRetained(retained);
client.publish(topic,message);
} catch (MqttException e) {
e.printStackTrace();
}
});
}
public void colse(){
mWorkHandler.post(()->{
try {
client.disconnect();
client.close();
} catch (MqttException e) {
e.printStackTrace();
}
});
}
}
运行实例,发送消息时,打印日志
医嘱消息
医嘱消息,是指客户端异常掉线,服务器给订阅者发送客户端异常掉线的信息,该信息由客户端定义。
- 嘟宝在连接MQTT之前,发布医嘱消息,主题是/dubao/will
- MQTT工具订阅医嘱主题/dubao/will 核心代码
protected void _connect(){
try {
String url= "tcp://"+MyConfig.mqttip+":"+MyConfig.mqttport;
String dubaoID="0001";
client=new MqttClient(url, "dubao_server"+dubaoID, new MemoryPersistence());
connOpts=new MqttConnectOptions();
connOpts.setCleanSession(true);// 重连接是否清理会话
connOpts.setConnectionTimeout(10);
connOpts.setKeepAliveInterval(60);//心跳间隔(秒)
connOpts.setAutomaticReconnect(true);
String s="嘟宝异常掉线了";
connOpts.setWill("/dubao/will",s.getBytes(StandardCharsets.UTF_8),0,false);
client.setCallback(callbackExtendedallback);
_connectionMQTTServer();
} catch (MqttException e) {
e.printStackTrace();
}
}
运行嘟宝app,异常退出,MQTT工具收到医嘱消息