概述
上节讲述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>