Android手把手编写儿童手机远程监控App之开机前台服务mqtt连接事例2

0 阅读5分钟

概述

上节完成在前台服务开启后,初始化连接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工具收到医嘱消息 在这里插入图片描述