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

0 阅读6分钟

概述

上节讲述app通过系统广播实现开机启动。Android 手机在开机会发出一条android.intent.action.BOOT_COMPLETED 的广播,app收到该广播启动自己的服务。实现开机启动。需实现以下一下五点:

  • 创建广播接收器BootReceiver
  • 在AndroidManifest.xml 注册监听开机广播及权限
  • BootReceiver类中处理开机广播消息
  • 手机设置允许app自启动 嘟宝作为后台程序,须一直与服务器保持长久,监听嘟妈指令,实现音视频通信。目前已经实现开机启动,前台服务。MQTT 是一种轻量级的发布/订阅(Publish/Subscribe)消息传输协议,专门为物联网设备、低带宽、不稳定网络环境设计。通过MQTT与服务器建立长久连接,并通过心跳包维持链路的稳定性。 在这里插入图片描述

Eclipse Mqtt

Eclipse Mqtt、 是一个由 Eclipse Foundation 提供的开源 Java 库,用来实现 MQTT 协议通信。 Android Java程序使用它可以连接 MQTT 服务器,并收发消息。

  • 在build.gradle添加第三方依赖库Eclipse Mqtt-
  • 创建MyMqttClient类,创建连接,监听主题
  • 前台服务初始化MyMqttClient,建立连接

在build.gradle添加工程依赖

在 Android 开发中,第三方依赖库指的是不是你自己写的、也不是系统自带的,而是由其他开发者或公司提供的代码库,你可以直接引入使用,来快速实现某些功能。如OkHttp、Picasso、Gson等。 添加路径:build.gradle有两个,

  • Project:DuBao,是主工程的build.gradle
  • Module:DuBao:app,是模块app的build.gradle。 路径:build.gradle(DuBao:app)> dependencies 添加依赖:
    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.2'

点击Sync Now 按钮,开始同步下载,若下载失败,大部分网络原因。 在这里插入图片描述 build.gradle(DuBao:app)代码

plugins {
    id 'com.android.application'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.zilong.dubao"
        minSdk 26
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.2'

    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

Eclipse Mqtt API

关于mqtt工具使用参看MQTT与Coturn详解

MqttClient构造函数

通过MqttClient函数,可以生成一个mqtt实例对象,以下是两个构造函数的声明:

// 使用内存持久化
MqttClient client = new MqttClient(brokerUrl, clientId, new MemoryPersistence());

// 或使用文件持久化(推荐生产环境)
MqttClient client = new MqttClient(brokerUrl, clientId, new MqttDefaultFilePersistence("/tmp/mqtt"));

brokerUrl参数,是连接地址,如:tcp://192.168.1.10:1883 clientId,id是连接的身份标识,可自定但不可重复。 第三个参数默认即可。

connect连接函数

开始通过mqtt协议连接服务器,若连接不上,则会堵塞连接,直到连接成功或者超时返回。函数声明如下 connect(new MqttConnectOptions()); MqttConnectOptions配置常用参数

MqttConnectOptions

setCleanSession(true)	         每次连接时清除之前的会话状态,推荐true(普通客户端)
setKeepAliveInterval(60)	    心跳间隔,单位秒,推荐30-120
setAutomaticReconnect(true)	     启用 SDK 内置自动重连,推荐true
setMaxInflight(1000)	           最大并发传输的消息数,根据吞吐量调整
setUserName()/setPassword()	   设置认证凭据,按需设置
setConnectionTimeout(30)	    连接超时时间(秒)范围10-30秒

setCallback

调用connect连接函数后,通过setCallback设置,监听连接事件如连接成功、掉线、消息等。函数声明如下: void setCallback(MqttCallbackExtended callback)

MqttCallbackExtended 接口

 private MqttCallbackExtended callbackExtendedallback =new MqttCallbackExtended() {
        @Override
        public void connectComplete(boolean reconnect, String serverURI) {
            Log.d("mqtt","connectComplete");
        }

        @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) {

        }
    };

创建MyMqttClient类,创建连接

  • 定义线程工作函数,用于mqtt连接与数据处理
  • 创建MqttClient、MqttConnectOptions、MqttCallbackExtended实例
  • 创建MyConfig,用于mqtt的地址端口,用户名密码配置
  • 创建连接函数
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");

        }

        @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;
            Log.d("mqtt",url);

            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="我异常离线,我的id是:"+dubaoID;
            connOpts.setWill("/axb/will",s.getBytes(StandardCharsets.UTF_8),0,false);
            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();
            }
        }
    }

}

前台服务初始化MyMqttClient,建立连接

嘟宝在开机启动后,启动前台服务,服务运行后,创建MyMqttClient进行连接。完成后台长连接任务 在这里插入图片描述 在连接成功后,发送一条消息给服务器。 前台服务代码

package com.zilong.dubao;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;

public class MyService extends Service {
    MyMqttClient myMqttClient;
    public MyService() {
    }


    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
        myMqttClient=new MyMqttClient();
        myMqttClient.connect();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {

        myMqttClient.colse();
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }


    private void createNotificationChannel() {
        NotificationChannel channel = new NotificationChannel(
                "DUBAO",
                "嘟宝安心守护孩子安全",
                NotificationManager.IMPORTANCE_LOW
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(channel);

        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                this, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        Notification notification= new NotificationCompat.Builder(this, "DUBAO")
                .setContentTitle("嘟宝")
                .setContentText("嘟宝安心守护孩子安全...")
                .setSmallIcon(android.R.drawable.ic_menu_info_details)
                .setContentIntent(pendingIntent)
                .setOngoing(true)  // 不可滑动删除
                .build();
        startForeground(10001, notification);
    }
}

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;
            Log.d("mqtt",url);

            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){
        mWorkHandler.post(()->{
            try {
                MqttMessage message=new MqttMessage(msg.getBytes(StandardCharsets.UTF_8));
                message.setQos(0);
                message.setRetained(false);
                client.publish(topic,message);
            } catch (MqttException e) {
                e.printStackTrace();
            }

        });
    }

    public void colse(){
        mWorkHandler.post(()->{
            try {
                client.disconnect();
                client.close();
            } catch (MqttException e) {
                e.printStackTrace();
            }

        });

    }

}

MyConfig代码

package com.zilong.dubao;

public class MyConfig {
    static String mqttip="192.168.1.20";
    static int mqttport=1883;
    static String mqttuname="admin";
    static String mqttpwd="";

}

运行查看效果: 控制台打印 在这里插入图片描述 MQTT调试工具收到消息 在这里插入图片描述

MQTT连接有个报错

在这里插入图片描述

2026-04-23 16:04:18.558 5789-5827/com.zilong.dubao D/mqtt: 连接失败
2026-04-23 16:04:18.571 5789-5827/com.zilong.dubao W/System.err: MqttException (0) - java.lang.SecurityException: Permission denied (missing INTERNET permission?)
2026-04-23 16:04:18.572 5789-5827/com.zilong.dubao W/System.err:     at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:38)
2026-04-23 16:04:18.572 5789-5827/com.zilong.dubao W/System.err:     at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:736)
2026-04-23 16:04:18.572 5789-5827/com.zilong.dubao W/System.err:     at org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule.start(TCPNetworkModule.java:72)
2026-04-23 16:04:18.572 5789-5827/com.zilong.dubao W/System.err:     at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:722)

错误原因在于没有授权网络通信,在AndroidManifest.xml添加权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.zilong.dubao">

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

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.intent.action.BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@drawable/fav"
        android:label="@string/app_name"
        android:roundIcon="@drawable/favround"
        android:supportsRtl="true"
        android:theme="@style/Theme.DuBao"
        tools:targetApi="31">
        <receiver
            android:name=".BootReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
        </receiver>

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" />
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>