网上大部分 Android 链接 Mqtt 都是使用 v3 版本,paho 的客户端也多年没有更新,能在网上到的 MQTTv5 的例子很少,也没有在安卓上实践的代码。
经过我长时间摸索,踩了不少坑,最终稳定的代码如问下。
import java.io.FileInputStream
import java.util.Properties
plugins {
id("com.android.application")
}
var SDK_DIR = System.getenv("ANDROID_SDK_HOME")
android {
namespace = "cn.netkiller.student"
compileSdk = 33
defaultConfig {
applicationId = "cn.netkiller.student"
minSdk = 30
targetSdk = 33
versionCode = 1
versionName = "1.3.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
if (SDK_DIR == null) {
var props = Properties()
props.load(FileInputStream(project.rootProject.file("local.properties")))
SDK_DIR = props.get("sdk.dir").toString()
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
}
applicationVariants.all {
var buildType = this.buildType.name
outputs.all {
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
if (buildType == "debug") {
this.outputFileName =
"netkiller-${baseName}-v${versionName}-${versionCode}.apk"
} else {
this.outputFileName = "netkiller-${baseName}-v${versionName}.apk"
}
println("OutputFileName: $this.outputFileName")
}
}
}
}
applicationVariants.all {
outputs.all {
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
this.outputFileName = "netkiller-${baseName}-v$versionName.apk"
println("OutputFileName: $this.outputFileName")
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
viewBinding = true
}
sourceSets {
named("main") {
jniLibs.srcDir("libs")
}
// val main by getting{
// jniLibs.srcDirs("src/main/libs")
jni.srcDirs()
// }
}
}
fun getLayoutLibPath() {
// return "${android.getSdkDirectory().getAbsolutePath()}" + "/platforms/" + android.compileSdkVersion + "/data/layoutlib.jar";
// return "${SDK_DIR}/platforms/" + android.compileSdkVersion + "/data/layoutlib.jar";
}
dependencies {
// compileOnly files ("${SDK_DIR}/platforms/android-24/data/layoutlib.jar")
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.8.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// implementation(project(mapOf("path" to ":ai")))
implementation("androidx.preference:preference:1.2.0")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.recyclerview:recyclerview:1.3.0")
// testImplementation("junit:junit:4.13.2")
// androidTestImplementation("androidx.test.ext:junit:1.1.5")
// androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// implementation("com.alibaba:fastjson:2.0.20.android")
implementation("com.auth0:java-jwt:4.4.0")
// implementation("org.projectlombok:lombok:1.18.32")
// implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
implementation("org.eclipse.paho:org.eclipse.paho.mqttv5.client:1.2.5")
implementation("org.eclipse.paho:org.eclipse.paho.android.service:1.1.1")
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp-sse
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:okhttp-sse:4.12.0")
implementation("com.google.code.gson:gson:2.10.1")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
// implementation("com.belerweb:pinyin4j:2.5.1")
// implementation("com.github.bumptech.glide:glide:4.16.0")
}
新手容易忽视的是 生命周期管理
onCreate 种启动 Service
onStop 种要关闭
onResume 恢复启动
onDestory 种断开有是 Mqtt 的链接
package cn.netkiller.student;
import static cn.netkiller.student.cloud.Api.token;
import static cn.netkiller.student.utils.AndroidManager.fullscreen;
import cn.netkiller.student.service.MessageQueueService;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private ActivityMainBinding binding;
private Intent messageQueueService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
messageQueueService = new Intent(this, MessageQueueService.class);
startService(messageQueueService);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// startForegroundService(new Intent(MainActivity.this, MessageQueueService.class));
// } else {
// startService(new Intent(MainActivity.this, MessageQueueService.class));
// }
}
@Override
protected void onDestroy() {
super.onDestroy();
stopService(messageQueueService);
}
@Override
public void onResume() {
super.onResume();
Config.Api.token = token();
startService(messageQueueService);
}
@Override
public void onStop() {
super.onStop();
stopService(messageQueueService);
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null) {
boolean isExit = intent.getBooleanExtra("QUIT", false);
if (isExit) {
this.finish();
}
}
}
}
Android Service 启动 MQTTv5 监听 Topic
package cn.netkiller.student.service;
import android.Manifest;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.google.gson.Gson;
import org.eclipse.paho.mqttv5.client.IMqttToken;
import org.eclipse.paho.mqttv5.client.MqttAsyncClient;
import org.eclipse.paho.mqttv5.client.MqttCallback;
import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse;
import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import cn.netkiller.student.MainActivity;
import cn.netkiller.student.R;
import cn.netkiller.student.ai.aigc.AigcSpeech;
import cn.netkiller.student.cloud.Cache;
import cn.netkiller.student.cloud.Device;
import cn.netkiller.student.config.Config;
import cn.netkiller.student.receiver.SendBroadcast;
public class MessageQueueService extends Service {
private static final String TAG = MessageQueueService.class.getName();
private final Gson gson = new Gson();
int qos = 2;
private String clientId;
private String topic;
private MqttAsyncClient mqttAsyncClient;
private final MqttCallback mqttCallback = new MqttCallback() {
@Override
public void disconnected(MqttDisconnectResponse disconnectResponse) {
Log.d(TAG, "disconnected " + disconnectResponse.toString());
}
@Override
public void mqttErrorOccurred(MqttException exception) {
Log.d(TAG, "mqttErrorOccurred " + exception.getMessage());
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String msg = new String(message.getPayload());
Log.d(TAG, String.format("messageArrived Topic: %s, Id: %s, QoS: %s, Message: %s, ", topic, message.getId(), message.getQos(), message));
if (topic.equals(Config.Api.appId + "/" + Config.Android.androidId + "/picture")) {
Map<String, String> map = gson.fromJson(msg, LinkedHashMap.class);
broadcastImage(map.get("session"), map.get("data"));
} else if (topic.equals(Config.Api.appId + "/" + Config.Android.androidId + "/audio")) {
Map<String, String> map = gson.fromJson(msg, LinkedHashMap.class);
broadcastAudio(map.get("session"), map.get("data"));
} else if (topic.equals(Config.Api.appId + "/" + Config.Android.androidId + "/story")) {
Map<String, String> map = gson.fromJson(msg, LinkedHashMap.class);
broadcastStory(map.get("session"), map.get("data"));
} else if (topic.equals(Config.Api.appId + "/" + Config.Android.androidId + "/warning")) {
broadcastAudio("", msg);
} else if (topic.equals(Config.Api.appId + "/" + Config.Android.androidId + "/notification") || topic.equals(Config.Api.appId + "/notification")) {
Map<String, String> map = gson.fromJson(msg, LinkedHashMap.class);
createNotification(map.get("title"), map.get("message"));
} else if (topic.equals(Config.Api.appId + "/".concat(Config.Android.androidId).concat("/call"))) {
AigcSpeech aigcSpeech = new AigcSpeech();
aigcSpeech.say(msg);
}
}
@Override
public void deliveryComplete(IMqttToken token) {
Log.d(TAG, "deliveryComplete");
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
Log.d(TAG, "connectComplete: " + topic + " reconnect: " + reconnect + " serverURI: " + serverURI);
}
@Override
public void authPacketArrived(int reasonCode, MqttProperties properties) {
Log.d(TAG, "authPacketArrived reasonCode: " + reasonCode + " properties: " + properties.toString());
}
};
public MessageQueueService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
topic = Config.Api.appId + "/" + Config.Android.androidId + "/#";
clientId = Config.Api.appId + "-" + Config.Android.androidId;
try {
MemoryPersistence persistence = new MemoryPersistence();
mqttAsyncClient = new MqttAsyncClient(Config.Mqtt.mqttServerUri, clientId, persistence);
MqttConnectionOptions mqttConnectionOptions = new MqttConnectionOptions();
mqttConnectionOptions.setUserName(Config.Mqtt.mqttUsername);
mqttConnectionOptions.setPassword(Config.Mqtt.mqttPassword.getBytes());
mqttConnectionOptions.setCleanStart(false);
mqttConnectionOptions.setConnectionTimeout(60);
mqttConnectionOptions.setKeepAliveInterval(30);
mqttConnectionOptions.setAutomaticReconnect(true);
mqttAsyncClient.setCallback(mqttCallback);
IMqttToken token = mqttAsyncClient.connect(mqttConnectionOptions);
token.waitForCompletion();
if (token.isComplete()) {
// Log.d(TAG, "Subscribe: " + topic);
mqttAsyncClient.subscribe(topic, qos);
}
} catch (MqttException me) {
Log.e(TAG, String.format("MqttException: %s, msg: %s, loc: %s, cause: %s", me.getReasonCode(), me.getMessage(), me.getLocalizedMessage(), me.getCause()));
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "isConnected " + mqttAsyncClient.isConnected());
if (mqttAsyncClient.isConnected()) {
try {
mqttAsyncClient.disconnect();
mqttAsyncClient.close();
} catch (MqttException e) {
Log.d(TAG, "onDestroy " + e);
}
Log.d(TAG, "Close client." + mqttAsyncClient.isConnected());
}
Log.d(TAG, "onDestroy.");
}
private void broadcastImage(String session, String url) {
Intent intent = new Intent();
intent.setAction("main.mqtt");
intent.putExtra("session", session);
intent.putExtra("image", url);
sendBroadcast(intent);
}
private void broadcastAudio(String session, String url) {
Intent intent = new Intent();
intent.setAction("main.mqtt");
intent.putExtra("session", session);
intent.putExtra("audio", url);
sendBroadcast(intent);
}
private void broadcastStory(String session, String text) {
Intent intent = new Intent();
intent.setAction("main.mqtt");
intent.putExtra("session", session);
intent.putExtra("story", text);
sendBroadcast(intent);
}
private void broadcastProgress(String progress) {
Intent intent = new Intent();
intent.setAction("main.progress");
intent.putExtra("progress", Integer.valueOf(progress));
sendBroadcast(intent);
}
private void toast(String message) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(() -> {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
});
}
public String logcat(String filter) {
StringBuffer buffer = new StringBuffer();
String command = "logcat -t 100";
if (!filter.isEmpty()) {
command.concat(String.format(" | grep %s", filter));
} else {
command.concat("");
}
try {
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String temp;
while ((temp = reader.readLine()) != null) {
buffer.append(temp).append("\n");
}
} catch (IOException e) {
Log.e(TAG, e.toString());
}
return buffer.toString();
}
private int createNotification(String title, String text) {
String channelId = "channelId";
String channelName = "channelName";
String description = "description";
String group = "group";
int notificationId = new Random().nextInt(101);
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(description);
notificationManagerCompat.createNotificationChannel(channel);
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle(title).setContentText(text)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
}
notificationManagerCompat.notify(notificationId, notification.build());
return notificationId;
}
}
最后祝你成功