一、MQTT原理
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议MQTT最大优点在于,用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT使用的发布/订阅消息模式,它提供了一对多的消息分发机制,从而实现与应用程序的解耦。
这是一种消息传递模式,消息不是直接从发送器发送到接收器(即点对点),而是由MQTT Broker分发的。
在设计中,阿里云服务器作为一个消息中转站,即
下位机→MQTT Broker→云平台(流转)→MQTT Broker →上位机
二、创建APP
提示:以下非零基础可以跳过
1.创建APP项目,选择Empty Activity
2.设置UI
创建完后会有一个空白的Activity,找到res/layout目录下的的activity_main.xml文件,在这里编写UI。
这里我的UI界面仅供参考,因为项目还有其他功能,这里只讲述MQTT的连接,不需要的BUTTON控件可以自行删除。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#078307"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="78dp"
android:layout_marginTop="30dp"
android:background="@color/green">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="基于人脸识别的小区门禁系统"
android:textColor="@color/white"
android:textSize="24sp" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FAF6F6"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center">
<ImageView
android:id="@+id/m_im_1"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:scaleType="fitXY"
android:src="@drawable/img" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginTop="30dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="325dp"
android:layout_height="60dp"
android:layout_centerInParent="true"
android:layout_marginBottom="20dp"
android:background="@drawable/login_shape"
android:gravity="center"
android:text="请选择验证方式"
android:textColor="@color/white"
android:textSize="30dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="220dp"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/Rfid"
android:layout_width="150dp"
android:layout_height="40dp"
android:background="@drawable/button_selector"
android:text="RFID刷卡验证"
android:textColor="@color/white" />
<Button
android:id="@+id/face_recognition"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginTop="15dp"
android:background="@drawable/button_selector"
android:text="人脸验证"
android:textColor="@color/white" />
<Button
android:id="@+id/password_check"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginTop="15dp"
android:background="@drawable/button_selector"
android:text="密码验证"
android:textColor="@color/white" />
<Button
android:id="@+id/exit"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginTop="15dp"
android:background="@drawable/button_selector"
android:text="退出"
android:textColor="@color/white" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="25dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MQTT连接状态:"
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/m_mqtt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" "
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
3.添加mqtt包的依赖
在项目的build.gradle 中添加一行implementation ‘org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.0’
如下图
4.联网权限配置
在AndroidManifest.xml文件中添加`
<uses-permission android:name="android.permission.INTERNET" />
<!--允许程序获取网络状态-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
到这里Android项目就创建完成啦
三、创建阿里云产品
登录阿里云平台后,根据指示找到物联网平台
进入后创建产品,有一个30天免费试用的产品,建议把支付宝的自动续费关闭。进入后就可以开始创建产品,具体产品的参数设置已经记不清了,当时的产品也已经被回收了,可以参考一下别的博客。
产品创建完成后需要创建设备,这里需要创建一个上位机和下位机,这里下位机用的是树莓派。
设备创建完成后在设备信息中会给出MQTT的连接参数,需要我们在Android APP和树莓派代码中进行配置。
在这里设备就创建好了,但是还没完成,还需要配置解析器。
这里需要用到的是云产品流转。
这里在云产品流转功能中创建了两个解析器,绑定数据源和数据目的,并编写解析器的脚本。数据源和数据目的根据解析器ID确定,解析器脚本即读取转发的数据。注意,解析器只能识别JSON格式数据和二进制,这里推荐使用JSON格式。
示例脚本如下:
var data = payload("json");
writeIotTopic(1004,"/ionmnFFCKzs/shumeipai/user/get",data);
通过payload函数,获取设备上报的消息内容,并按照JSON格式转换。
然后将消息发送给树莓派接收的Topic。
通过日志服务可以监控到消息转发的过程,如下图所示:
这里消息质量使用的是Qos1,至少保证传一次。
数据流如下:
四、编写代码
现在到了编写代码部分,如果你的功能较多,需要跳转页面,建议把MQTT挂在后台,防止切屏的时候MQTT连接断开,从而导致数据丢包。
首先是配置MQTT的参数配置,根据上文中MQTT配置信息复制粘贴过去。
添加代码全面飘红?不要慌,因为你还没有完成构建,很多类还没导入,逐个alt+enter导入即可。
private Handler handler;
private SqliteDBHelper mHelper;
private MqttClient client;
private final String host = "";
private final String userName = "";
private final String passWord = "";
private final String ClientId = "";
private final String mqtt_sub_topic = "";//订阅话题
private final String mqtt_pub_topic = "";//发布话题
private MqttConnectOptions mqttConnectOptions;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Objects.requireNonNull(getSupportActionBar()).hide();//这种方式默认式亮色主题
setContentView(R.layout.activity_main_desk);
TextView m_mqtt = findViewById(R.id.m_mqtt);//获取控件,不需要的控件自行删除
Button Rfid = findViewById(R.id.Rfid);
Button face_recognition = findViewById(R.id.face_recognition);
Button password_check = findViewById(R.id.password_check);
Button exit = findViewById(R.id.exit);
Mqtt_init();
startReconnect();
handler = new Handler(Looper.myLooper()) {//用于处理
@SuppressLint("SetTextI18n")
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1: //开机校验更新回传
break;
case 2: // 反馈回传
break;
case 3: //MQTT 收到消息回传
System.out.println(msg.obj.toString()); // 显示MQTT数据
break;
case 31: //连接成功
m_mqtt.setText("连接成功");//连接成功后按钮变为可点击状态
Rfid.setOnClickListener(MainDeskActivity.this);
face_recognition.setOnClickListener(MainDeskActivity.this);
password_check.setOnClickListener(MainDeskActivity.this);
exit.setOnClickListener(MainDeskActivity.this);
try {
client.subscribe(mqtt_sub_topic, 1);//订阅
} catch (MqttException e) {
e.printStackTrace();
}
break;
case 30: //连接失败
Toast.makeText(MainDeskActivity.this, "连接失败", Toast.LENGTH_SHORT).show();
m_mqtt.setText("连接失败");
break;
default:
break;
}
}
};
}
Mqtt_init函数
需要注意的一点是如果你的MQTT连接断开后就无法重连上,建议将会话心跳设置长一点
private void Mqtt_init() {
try {
client = new MqttClient(host, ClientId, new MemoryPersistence());
//MQTT的连接设置
mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setCleanSession(false);
mqttConnectOptions.setUserName(userName);
mqttConnectOptions.setPassword(passWord.toCharArray());
mqttConnectOptions.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*30秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
//由于自身网络延时很难确定,建议设大一点,防止断开连接后无法重连
mqttConnectOptions.setKeepAliveInterval(30);
//设置回调
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
//连接丢失后,一般在这里面进行重连
System.out.println("connectionLost----------");
startReconnect();
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
//publish后会执行到这里
System.out.println("deliveryComplete---------"
+ token.isComplete());
}
@Override
public void messageArrived(String topicName, MqttMessage message)
throws Exception {
//subscribe后得到的消息会执行到这里面
System.out.println("messageArrived----------");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
MQTT连接函数
// MQTT连接函数
private void Mqtt_connect() {
new Thread(new Runnable() {
@Override
public void run() {
try {
if (!client.isConnected()) //如果还未连接
{
client.connect(mqttConnectOptions);
Message msg = new Message();
msg.what = 31;
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
e.getMessage();
Message msg = new Message();
msg.what = 30;
handler.sendMessage(msg);
}
}
}).start();
}
MQTT发布订阅
// 订阅函数 (下发任务/命令)
private void publish_message_plus(String topic, String message2) {
if (client == null || !client.isConnected()) {
return;
}
MqttMessage message = new MqttMessage();
message.setPayload(message2.getBytes());
message.setQos(1);//设置消息质量
//MQTT一共有三种消息质量
//Qos0:会发生消息的丢失或重复
//Qos1:至少送达一次
//Qos2:保证只送达到目标端一次,网络开销最高
try {
client.publish(topic, message);
} catch (MqttException e) {
e.printStackTrace();
}
}
附上JSON数据的处理,其中message是MessageArrived中收到的数据,将message转为jsonObject对象后通过键获取值。
这里只用到了简单的处理。假设收到的数据是"{“flag”:“02”}\n"
JSONObject jsonObject = new JSONObject(message.toString());
int flag = jsonObject.getString("flag");
五、完成测试
以上准备工作完成后就可以进行测试了,这里可以采用MQTT.fx软件来模拟下位机来发送信息,官网下载最新版http://mqttfx.org,由于只有三个月的免费试用,我的已经做不了演示了,详细操作可以自行网络搜索。联调成功后就可以对下位机进行配置了。
MQTT连接成功后如下图所示:
可以根据自己功能的需要添加点击事件以及消息处理的逻辑,有问题欢迎私信。
总结
本文主要讲述了利用MQTT协议实现Android app与阿里云平台的连接,流程并不难,新手会因为不熟悉而踩一些坑。在调试过程中,建议利用MQTT.fx分别对上位机和下位机调试,便于排查问题。消息接收失败可以在云平台上和MQTT.fx的日志里追踪消息。难点需要掌握Handler的回传机制,便于进行逻辑的编写。