package com.pinappletech.android.main; import android.app.NotificationManager; import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.Looper; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.PowerManager; import android.net.wifi.WifiManager; import android.util.Log; import android.os.Build; import android.app.NotificationChannel; import android.app.Notification; import android.content.Context; import android.os.RemoteException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import com.unity3d.player.UnityPlayer; import com.pvr.tobservice.interfaces.IToBServiceProxy; import com.pvr.tobservice.interfaces.IIntCallback; import com.pvr.tobservice.ToBServiceHelper; import com.pvr.tobservice.enums.PBS_SystemInfoEnum; import java.util.ArrayList; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; 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; public class Main extends Service { private final String TAG = "PineappleService"; // ================= UDP服务 ================= // ip地址 private String localIp = "0.0.0.0"; // UDP监听端口 private int udpReceivePort = 9988; private int udpSendPort = 9987; private DatagramSocket udpSocket; private boolean isRunning = false; // 添加缓冲区大小常量定义 private final int bufferSize = 1024; private final ExecutorService networkExecutor = Executors.newFixedThreadPool(2); // 设备状态 private DeviceInfo deviceInfo = null; // 包名映射 private Map packageNamesMap = new HashMap<>(); private List packageNamesList = new ArrayList<>(); private String packageNames = "{}"; // 服务context private android.content.Context serviceContext; // ================= 设备信息类 ================= private class DeviceInfo { String ip; String sn; int power; int status; // 0: 离线 1: 在线 2: 游玩中 String playing; DeviceInfo(String ip, String sn, int power, int status, String playing) { this.ip = ip; this.sn = sn; this.power = power; this.status = status; this.playing = playing; } } // ================= Http 服务 ================= private ServerSocket httpServerSocket; private ExecutorService httpExecutor = Executors.newFixedThreadPool(4); private boolean httpServerRunning = false; private int httpPort = 9999; private ToBServiceHelper toBServiceHelper; // ================= MQTT 服务 ================= private MqttClient mqttClient; private static final String MQTT_BROKER_URL = "ws://emqx.pineappletech.cn"; private static final String MQTT_CLIENT_ID_PREFIX = "pico_"; private ScheduledExecutorService mqttScheduler; private static final long MQTT_REPORT_INTERVAL = 20; // 20秒上报一次 private static final int MQTT_KEEP_ALIVE = 60; // KeepAlive 60秒 private static final int MQTT_CONNECTION_TIMEOUT = 30; // 连接超时30秒 private volatile boolean isMqttReconnecting = false; private int mqttReconnectAttempts = 0; private static final int MAX_RECONNECT_ATTEMPTS = 10; private static final long RECONNECT_DELAY_MS = 5000; // 重连延迟5秒 // ================= 唤醒锁 ================= private PowerManager.WakeLock wakeLock; private WifiManager.WifiLock wifiLock; private static final String WAKE_LOCK_TAG = "PineappleService::WakeLock"; // ================= 服务生命周期 ================= @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { Log.i(TAG, "菠萝服务启动"); // pico企业服务绑定 toBServiceHelper = ToBServiceHelper.getInstance(); toBServiceHelper.bindTobService(this); toBServiceHelper.setBindCallBack(new ToBServiceHelper.BindCallBack() { @Override public void bindCallBack(Boolean status) { // 绑定结果回调,绑定成功之后才能调用接口 Log.i(TAG, "绑定pico企业服务 : " + status); if (status) { try { toBServiceHelper.getServiceBinder().pbsAppKeepAlive("com.pineapplegame.service", true, 0); Log.i(TAG, "启动应用保活"); } catch (android.os.RemoteException e) { Log.e(TAG, "启动应用保活失败: " + e.getMessage()); } } } }); serviceContext = this; deviceInfo = new DeviceInfo("0.0.0.0", "未知序列号", 0, 0, "未知游戏"); // 初始化本机IP地址 String ip = getIpAddressFromPico(); localIp = ip; deviceInfo.ip = ip; // 创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "pineapple_channel", "菠萝服务", NotificationManager.IMPORTANCE_LOW); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.createNotificationChannel(channel); } Notification notification = new Notification.Builder(this, "pineapple_channel") .setContentTitle("菠萝服务") .setContentText("运行中...") .setSmallIcon(android.R.drawable.ic_notification_overlay) .build(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC); } else { startForeground(1, notification); } // 启动Udp服务 startUdpReceiver(); // 启动Http服务 startHttpReceiver(); // 初始化唤醒锁 initWakeLocks(); // 启动MQTT服务 initMqttService(); // return START_STICKY; } /** * 初始化唤醒锁,防止设备进入 Doze 模式导致 MQTT 断线 */ private void initWakeLocks() { try { // 获取 PowerManager 并创建 WakeLock PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG); wakeLock.setReferenceCounted(false); wakeLock.acquire(); Log.i(TAG, "唤醒锁已获取"); // 获取 WifiManager 并创建 WifiLock WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, WAKE_LOCK_TAG); wifiLock.setReferenceCounted(false); wifiLock.acquire(); Log.i(TAG, "WiFi 锁已获取"); } catch (Exception e) { Log.e(TAG, "初始化唤醒锁失败: " + e.getMessage()); } } /** * 释放唤醒锁 */ private void releaseWakeLocks() { try { if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); Log.i(TAG, "唤醒锁已释放"); } if (wifiLock != null && wifiLock.isHeld()) { wifiLock.release(); Log.i(TAG, "WiFi 锁已释放"); } } catch (Exception e) { Log.e(TAG, "释放唤醒锁失败: " + e.getMessage()); } } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "菠萝服务停止"); // 清理资源 isRunning = false; httpServerRunning = false; // 关闭网络执行器 networkExecutor.shutdownNow(); // 关闭UDP socket if (udpSocket != null && !udpSocket.isClosed()) { udpSocket.close(); } // 关闭HTTP服务器socket try { if (httpServerSocket != null && !httpServerSocket.isClosed()) { httpServerSocket.close(); } } catch (IOException e) { Log.e(TAG, "关闭HTTP服务器socket时出错: " + e.getMessage()); } // 关闭HTTP执行器 if (httpExecutor != null) { httpExecutor.shutdownNow(); } // 断开MQTT连接 disconnectMqtt(); // 释放唤醒锁 releaseWakeLocks(); } private String getIpAddressFromPico() { String ip = "0.0.0.0"; try { // 尝试通过Pico的系统信息服务获取IP地址 ip = toBServiceHelper.getServiceBinder().pbsStateGetDeviceInfo( PBS_SystemInfoEnum.DEVICE_IP, 0); Log.i(TAG, "通过Pico服务获取到IP地址: " + ip); } catch (Exception e) { Log.e(TAG, "通过Pico服务获取IP失败: " + e.getMessage()); } return ip; } // 修改 isLocalAddress 方法为 isLocalMessage,增加端口判断 private boolean isLocalMessage(InetAddress address, int port) { // 只有当地址是本机IP且端口是8888时才屏蔽 // return localIPs.contains(address.getHostAddress()) && port == localPort; return localIp.equals(address.getHostAddress()) && port == udpReceivePort; } // 根据包名获取应用名称 private String getAppName(String packageName) { try { android.content.pm.PackageManager pm = getPackageManager(); android.content.pm.ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0); return pm.getApplicationLabel(appInfo).toString(); } catch (android.content.pm.PackageManager.NameNotFoundException e) { Log.e(TAG, "应用名称获取失败,包名: " + packageName + ", 错误: " + e.getMessage()); return "未知应用"; } } // 获取已安装的包名 private void getInstalledApps() { try { // 获取包管理器 android.content.pm.PackageManager pm = getPackageManager(); // 获取已安装的应用列表 java.util.List packages = pm.getInstalledPackages(0); // 创建一个Map来存储包名和应用名称的映射 packageNamesMap.clear(); packageNamesList.clear(); for (android.content.pm.PackageInfo packageInfo : packages) { // 过滤掉系统应用,只显示用户安装的应用 if ((packageInfo.applicationInfo.flags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) { // 只保留包名中包含"pineappletech"的应用 if (packageInfo.packageName.contains("pineappletech") && !packageInfo.packageName.contains("service")) { // 获取应用名称 String appName = getAppName(packageInfo.packageName); packageNamesMap.put(packageInfo.packageName, appName); packageNamesList.add(packageInfo.packageName); } } } // 将Map转换为指定格式的JSON数组字符串 StringBuilder jsonBuilder = new StringBuilder(); jsonBuilder.append("{\"apps\":["); boolean first = true; for (Map.Entry entry : packageNamesMap.entrySet()) { if (!first) { jsonBuilder.append(","); } // 构建 { "name": "应用名称", "address": "包名" } 格式 jsonBuilder.append("{") .append("\"name\":\"").append(entry.getValue()).append("\",") .append("\"address\":\"").append(entry.getKey()).append("\"") .append("}"); first = false; } jsonBuilder.append("]}"); Log.i(TAG, "=== 已安装的应用包名 ==="); Log.i(TAG, "JSON格式: " + jsonBuilder.toString()); packageNames = jsonBuilder.toString(); Log.i(TAG, "=== 应用包名列表结束 ==="); } catch (Exception e) { Log.e(TAG, "获取已安装应用列表失败: " + e.getMessage()); } } // 处理接收到的消息 private void handleUdpReceivedMessage(String message, InetAddress senderAddress, int senderPort) { Log.i(TAG, "收到消息 -> " + message); handleBoardcastMessage(message, senderAddress, senderPort); } private void handleBoardcastMessage(String message, InetAddress senderAddress, int senderPort) { // 不处理来自本机相同端口的消息 if (isLocalMessage(senderAddress, senderPort)) { return; } if ("DISCOVER".equals(message)) { Log.i(TAG, "收到设备发现请求"); if (deviceInfo.status == 0) { // 更新设备状态为在线 deviceInfo.status = 1; } getDeviceInfoAsJson(); // 使用主线程池来发送回复,避免并发问题 networkExecutor.execute(() -> { DatagramSocket replySocket = null; try { // 创建一个新的UDP套接字用于回复 replySocket = new DatagramSocket(udpSendPort); // 准备回复消息,包含sn号 String responseMessage = deviceInfo.sn; byte[] data = responseMessage.getBytes("UTF-8"); DatagramPacket packet = new DatagramPacket( data, data.length, senderAddress, 8988); replySocket.send(packet); Log.i(TAG, "回复设备发现请求到: " + senderAddress.getHostAddress() + ":" + 8988); } catch (Exception e) { Log.e(TAG, "设备发现回复失败: " + e.getMessage()); e.printStackTrace(); } finally { if (replySocket != null && !replySocket.isClosed()) { replySocket.close(); } } }); } } private void startUdpReceiver() { Log.i(TAG, "启动UDP接收器"); isRunning = true; networkExecutor.execute(() -> { try { udpSocket = new DatagramSocket(udpReceivePort); udpReceivePort = udpSocket.getLocalPort(); // 记录本地端口号 byte[] buffer = new byte[bufferSize]; while (isRunning) { DatagramPacket packet = new DatagramPacket(buffer, buffer.length); udpSocket.receive(packet); String receivedData = new String( packet.getData(), 0, packet.getLength()).trim(); // 修改为使用新的判断方法 if (!isLocalMessage(packet.getAddress(), packet.getPort())) { handleUdpReceivedMessage( receivedData, packet.getAddress(), packet.getPort()); } else { Log.d(TAG, "忽略来自本机相同端口的消息: " + packet.getAddress().getHostAddress() + ":" + packet.getPort()); } } } catch (Exception e) { if (isRunning) { Log.e(TAG, "UDP接收错误: " + e.getMessage()); } } finally { if (udpSocket != null && !udpSocket.isClosed()) { udpSocket.close(); } } }); } // 建议callback,单独创建。防止多次调用init后,会绑定多个callback。 public void initPicoCast() { try { int result = ToBServiceHelper.getInstance().getServiceBinder().pbsPicoCastInit(callback, 0); Log.i(TAG, "pico投屏初始化 " + result); } catch (RemoteException e) { e.printStackTrace(); } } private IIntCallback callback = new IIntCallback.Stub() { @Override public void callback(int result) throws RemoteException { Log.i(TAG, "pico投屏回调 " + result); } }; private void startHttpReceiver() { Log.i(TAG, "启动HTTP接收器"); httpExecutor.execute(() -> { try { httpServerSocket = new ServerSocket(httpPort); httpServerRunning = true; Log.i(TAG, "HTTP服务器启动,端口: " + httpPort); while (httpServerRunning) { try { Socket clientSocket = httpServerSocket.accept(); httpExecutor.execute(() -> handleHttpRequest(clientSocket)); } catch (IOException e) { if (httpServerRunning) { Log.e(TAG, "处理HTTP请求时出错: " + e.getMessage()); } } } } catch (IOException e) { Log.e(TAG, "无法启动HTTP服务器: " + e.getMessage()); } }); } private void handleHttpRequest(Socket clientSocket) { try { java.io.BufferedReader in = new java.io.BufferedReader( new java.io.InputStreamReader(clientSocket.getInputStream())); // 解析请求行 String requestLine = in.readLine(); if (requestLine == null || requestLine.isEmpty()) { clientSocket.close(); return; } // 提取请求方法和路径 String[] requestParts = requestLine.split(" "); String method = requestParts[0]; String path = requestParts[1]; Log.i(TAG, "收到HTTP请求: " + requestLine); // 解析请求头 Map headers = new HashMap<>(); String headerLine; int contentLength = 0; while ((headerLine = in.readLine()) != null && !headerLine.isEmpty()) { String[] parts = headerLine.split(":", 2); if (parts.length == 2) { headers.put(parts[0].trim(), parts[1].trim()); if ("Content-Length".equalsIgnoreCase(parts[0].trim())) { contentLength = Integer.parseInt(parts[1].trim()); } } } // 读取请求体(如果有) StringBuilder body = new StringBuilder(); if (contentLength > 0) { char[] buffer = new char[contentLength]; int totalRead = 0; while (totalRead < contentLength) { int read = in.read(buffer, totalRead, contentLength - totalRead); if (read == -1) break; totalRead += read; } body.append(buffer, 0, totalRead); } Log.i(TAG, "请求体: " + body.toString()); // 处理预检请求(OPTIONS) String responseContent = ""; int responseCode = 200; if ("OPTIONS".equalsIgnoreCase(method)) { // 对于预检请求,直接返回成功 responseContent = "{\"status\":\"preflight-success\"}"; } else { // 根据请求路径和方法处理请求 // 解析请求体中的JSON数据 try { org.json.JSONObject jsonBody = new org.json.JSONObject(body.toString()); String intent = jsonBody.optString("intent"); // 获取所有包信息 if ("getPackageInfos".equals(intent)) { Log.i(TAG, "执行获取所有包信息"); // 修复后的代码: getInstalledApps(); // 更新包信息 responseContent = "{\"code\":200,\"message\":\"获取成功\",\"data\":" + packageNames + "}"; } // 播放影片 else if ("playFilm".equals(intent)) { String packageName = jsonBody.optString("packageName"); Log.i(TAG, "执行播放: " + packageName); // 点亮屏幕 try { toBServiceHelper.getServiceBinder().pbsScreenOn(); Log.i(TAG, "屏幕点亮命令发送成功"); } catch (android.os.RemoteException e) { Log.e(TAG, "调用pbsScreenOn失败: " + e.getMessage()); } launchAppDirectly(packageName); deviceInfo.status = 2; deviceInfo.playing = packageNamesMap.get(packageName); responseContent = "{\"code\":200,\"message\":\"已执行播放\"}"; } // 停止播放 else if ("stopFilm".equals(intent)) { Log.i(TAG, "执行停止播放"); // 点亮屏幕 try { toBServiceHelper.getServiceBinder().pbsScreenOn(); Log.i(TAG, "屏幕点亮命令发送成功"); } catch (android.os.RemoteException e) { Log.e(TAG, "调用pbsScreenOn失败: " + e.getMessage()); } forceStopAllPineappleApps(); responseContent = "{\"code\":200,\"message\":\"已执行停止播放\"}"; } // ping else if ("ping".equals(intent)) { Log.i(TAG, "执行ping"); responseContent = "{\"code\":200,\"message\":\"成功连接\",\"data\":\"" + getDeviceInfoAsJson() + "\"}"; } } catch (org.json.JSONException e) { Log.e(TAG, "解析请求体JSON失败: " + e.getMessage()); responseContent = "{\"status\":\"fail\",\"message\":\"解析失败\"}"; responseCode = 400; } } // 发送响应 OutputStream out = clientSocket.getOutputStream(); String httpResponse = "HTTP/1.1 " + responseCode + " OK\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\r\n" + "Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With\r\n" + "Access-Control-Max-Age: 3600\r\n" + "Content-Length: " + responseContent.getBytes("UTF-8").length + "\r\n" + "\r\n" + responseContent; out.write(httpResponse.getBytes("UTF-8")); out.flush(); out.close(); clientSocket.close(); } catch ( IOException e) { Log.e(TAG, "处理HTTP客户端连接时出错: " + e.getMessage()); try { clientSocket.close(); } catch (IOException ioException) { Log.e(TAG, "关闭客户端连接时出错: " + ioException.getMessage()); } } } // // 然后修改launchAppDirectly方法使用Service的Context // public void launchAppDirectly(String packageName) { // Log.i(TAG, "直接启动应用: " + packageName); // try { // // 使用Service自己的Context而不是UnityPlayer的Activity // if (serviceContext == null) { // Log.e(TAG, "Service Context为空"); // return; // } // forceStopAllPineappleApps(); // try { // Thread.sleep(500); // } catch (InterruptedException e) { // // 忽略 // } // // 使用Service Context启动应用 // android.content.Intent launchIntent = serviceContext.getPackageManager() // .getLaunchIntentForPackage(packageName); // if (launchIntent != null) { // // 添加传递给目标应用的参数 // launchIntent.putExtra("service_ip", deviceInfo.ip); // launchIntent.putExtra("service_port", httpPort); // launchIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK); // launchIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP); // launchIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); // launchIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT); // serviceContext.startActivity(launchIntent); // Log.i(TAG, "成功启动应用: " + packageName); // } else { // Log.e(TAG, "无法找到应用的启动Intent: " + packageName); // } // } catch (Exception e) { // Log.e(TAG, "启动应用失败: " + e.getMessage()); // e.printStackTrace(); // } // } // 然后修改launchAppDirectly方法使用Service的Context public void launchAppDirectly(String packageName) { Log.i(TAG, "直接启动应用: " + packageName); try { // 使用Pico企业服务启动应用 if (toBServiceHelper == null || toBServiceHelper.getServiceBinder() == null) { Log.e(TAG, "ToB Service Helper 或 Binder 为空"); return; } forceStopAllPineappleApps(); try { Thread.sleep(500); } catch (InterruptedException e) { // 忽略 } // 使用Pico的企业服务API启动应用 String action = "picovr.intent.action.player"; // 根据需要调整action String extra = "{\"uri\":\"/sdcard/test.mp4\",\"videoType\":0,\"videoSource\":1}"; // 这里需要根据实际需求调整参数 String[] categories = { android.content.Intent.CATEGORY_DEFAULT }; int[] flags = { android.content.Intent.FLAG_ACTIVITY_NEW_TASK }; int ext = 0; // 如果packageName是实际的应用包名,可以用它替换action参数 // 目前使用您提供的Pico API格式 toBServiceHelper.getServiceBinder().pbsStartActivity( packageName, // packageName "com.unity3d.player.UnityPlayerActivity", // className - 留空让系统自动选择主Activity action, // action extra, // extra - JSON格式的额外参数 categories, // categories flags, // flags ext // ext ); Log.i(TAG, "通过Pico企业服务启动应用: " + packageName + " with action: " + action); // 更新设备状态 deviceInfo.status = 2; deviceInfo.playing = packageNamesMap.get(packageName); } catch (Exception e) { Log.e(TAG, "通过Pico服务启动应用失败: " + e.getMessage()); e.printStackTrace(); } } public void forceStopAllPineappleApps() { Log.i(TAG, "强制停止所有Pineapple应用..."); // 准备参数 int[] pids = {}; // 要终止的进程ID数组 String[] packageNames = packageNamesList.toArray(new String[packageNamesList.size()]); int ext = 0; // 扩展参数,根据文档使用 try { toBServiceHelper.getServiceBinder().pbsKillAppsByPidOrPackageName(pids, packageNames, ext); } catch (android.os.RemoteException e) { Log.e(TAG, "调用pbsKillAppsByPidOrPackageName失败: " + e.getMessage()); e.printStackTrace(); } catch (Exception e) { Log.e(TAG, "调用pbsKillAppsByPidOrPackageName时发生未知错误: " + e.getMessage()); e.printStackTrace(); } } // ================= Unity通信方法 ================= public void callUnity(String gameObjectName, String methodName, String message) { try { UnityPlayer.currentActivity.runOnUiThread(() -> { UnityPlayer.UnitySendMessage(gameObjectName, methodName, message); }); } catch (Exception e) { Log.e(TAG, "调用Unity方法失败: " + e.getMessage()); } } // ================= 静态方法(Unity调用)================= public static void startMyForegroundService() { Log.i("PineappleService", "开始启动服务..."); try { Class unityPlayerClass = Class.forName("com.unity3d.player.UnityPlayer"); Field activityField = unityPlayerClass.getField("currentActivity"); Object activity = activityField.get(null); Method getApplicationContextMethod = activity.getClass().getMethod("getApplicationContext"); Context context = (Context) getApplicationContextMethod.invoke(activity); Intent intent = new Intent(context, Main.class); context.startForegroundService(intent); } catch (Exception e) { Log.e("PineappleService", "启动服务失败: " + e.getMessage()); } } private String getDeviceInfoAsJson() { try { deviceInfo.ip = getIpAddressFromPico(); try { deviceInfo.sn = toBServiceHelper.getServiceBinder().pbsStateGetDeviceInfo( PBS_SystemInfoEnum.EQUIPMENT_SN, 0); } catch (android.os.RemoteException e) { Log.e(TAG, "获取设备序列号失败: " + e.getMessage()); deviceInfo.sn = "未知序列号"; } // 获取电量信息字符串 String powerStr; try { powerStr = toBServiceHelper.getServiceBinder().pbsStateGetDeviceInfo( PBS_SystemInfoEnum.ELECTRIC_QUANTITY, 0); } catch (android.os.RemoteException e) { Log.e(TAG, "获取电量信息失败: " + e.getMessage()); powerStr = "0"; } // 将字符串转换为整数 try { deviceInfo.power = Integer.parseInt(powerStr); } catch (NumberFormatException e) { Log.e(TAG, "解析电量信息失败: " + powerStr + ", 错误: " + e.getMessage()); deviceInfo.power = 0; // 默认值 } org.json.JSONObject data = new org.json.JSONObject(); data.put("ip", deviceInfo.ip); data.put("sn", deviceInfo.sn); data.put("power", deviceInfo.power); data.put("status", deviceInfo.status); data.put("playing", deviceInfo.playing); return data.toString().replace("\"", "\\\""); } catch (org.json.JSONException e) { Log.e(TAG, "序列化设备信息失败: " + e.getMessage()); return "{}"; } } // ================= MQTT 服务方法 ================= /** * 初始化 MQTT 服务 */ private void initMqttService() { Log.i(TAG, "初始化 MQTT 服务..."); mqttScheduler = Executors.newSingleThreadScheduledExecutor(); connectToMqttBroker(); } /** * 连接到 MQTT Broker */ private void connectToMqttBroker() { networkExecutor.execute(() -> { try { // 获取设备序列号作为客户端ID String clientId = MQTT_CLIENT_ID_PREFIX + System.currentTimeMillis(); try { if (toBServiceHelper != null && toBServiceHelper.getServiceBinder() != null) { String sn = toBServiceHelper.getServiceBinder().pbsStateGetDeviceInfo( PBS_SystemInfoEnum.EQUIPMENT_SN, 0); if (sn != null && !sn.isEmpty() && !"未知序列号".equals(sn)) { clientId = MQTT_CLIENT_ID_PREFIX + sn; } } } catch (Exception e) { Log.w(TAG, "获取序列号失败,使用默认客户端ID: " + e.getMessage()); } mqttClient = new MqttClient(MQTT_BROKER_URL, clientId, new MemoryPersistence()); MqttConnectOptions options = new MqttConnectOptions(); options.setCleanSession(true); options.setConnectionTimeout(MQTT_CONNECTION_TIMEOUT); options.setKeepAliveInterval(MQTT_KEEP_ALIVE); options.setAutomaticReconnect(true); // 设置最大重连间隔为60秒 options.setMaxReconnectDelay(60000); mqttClient.setCallback(new MqttCallback() { @Override public void connectionLost(Throwable cause) { Log.e(TAG, "MQTT 连接丢失: " + cause.getMessage()); // 触发重连 scheduleMqttReconnect(); } @Override public void messageArrived(String topic, MqttMessage message) { Log.i(TAG, "收到 MQTT 消息 - Topic: " + topic + ", Message: " + new String(message.getPayload())); } @Override public void deliveryComplete(IMqttDeliveryToken token) { // 消息发送完成 } }); mqttClient.connect(options); Log.i(TAG, "MQTT 连接成功 - ClientID: " + clientId); // 重置重连计数 mqttReconnectAttempts = 0; isMqttReconnecting = false; // 启动定时上报任务 startMqttReportTask(); } catch (MqttException e) { Log.e(TAG, "MQTT 连接失败: " + e.getMessage()); // 连接失败也触发重连 scheduleMqttReconnect(); } }); } /** * 调度 MQTT 重连 */ private void scheduleMqttReconnect() { if (isMqttReconnecting) { Log.d(TAG, "MQTT 重连已在进行中,跳过"); return; } if (mqttReconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { Log.e(TAG, "MQTT 重连次数超过最大限制,停止重连"); return; } isMqttReconnecting = true; mqttReconnectAttempts++; long delay = Math.min(RECONNECT_DELAY_MS * mqttReconnectAttempts, 60000); // 最大延迟60秒 Log.i(TAG, "MQTT 将在 " + delay + "ms 后进行第 " + mqttReconnectAttempts + " 次重连"); networkExecutor.execute(() -> { try { Thread.sleep(delay); // 清理旧连接 if (mqttClient != null) { try { mqttClient.disconnectForcibly(); mqttClient.close(); } catch (Exception e) { // 忽略清理错误 } mqttClient = null; } // 重新连接 isMqttReconnecting = false; connectToMqttBroker(); } catch (InterruptedException e) { Log.e(TAG, "MQTT 重连等待被中断: " + e.getMessage()); isMqttReconnecting = false; } }); } /** * 启动 MQTT 定时上报任务 */ private void startMqttReportTask() { mqttScheduler.scheduleAtFixedRate(() -> { try { if (mqttClient != null && mqttClient.isConnected()) { publishDeviceStatus(); } else { Log.w(TAG, "MQTT 未连接,跳过上报"); } } catch (Exception e) { Log.e(TAG, "MQTT 上报任务异常: " + e.getMessage()); } }, 0, MQTT_REPORT_INTERVAL, TimeUnit.SECONDS); Log.i(TAG, "MQTT 定时上报任务已启动,间隔: " + MQTT_REPORT_INTERVAL + " 秒"); } /** * 发布设备状态到 MQTT */ private void publishDeviceStatus() { try { // 更新设备信息 String deviceJson = getDeviceInfoAsJsonForMqtt(); String topic = "device/" + deviceInfo.sn + "/status"; MqttMessage message = new MqttMessage(deviceJson.getBytes("UTF-8")); message.setQos(0); message.setRetained(false); mqttClient.publish(topic, message); Log.d(TAG, "MQTT 上报成功 - Topic: " + topic); } catch (Exception e) { Log.e(TAG, "MQTT 上报失败: " + e.getMessage()); } } /** * 获取设备信息 JSON(用于 MQTT 上报) */ private String getDeviceInfoAsJsonForMqtt() { try { deviceInfo.ip = getIpAddressFromPico(); try { deviceInfo.sn = toBServiceHelper.getServiceBinder().pbsStateGetDeviceInfo( PBS_SystemInfoEnum.EQUIPMENT_SN, 0); } catch (android.os.RemoteException e) { Log.e(TAG, "获取设备序列号失败: " + e.getMessage()); deviceInfo.sn = "未知序列号"; } // 获取电量信息字符串 String powerStr; try { powerStr = toBServiceHelper.getServiceBinder().pbsStateGetDeviceInfo( PBS_SystemInfoEnum.ELECTRIC_QUANTITY, 0); } catch (android.os.RemoteException e) { Log.e(TAG, "获取电量信息失败: " + e.getMessage()); powerStr = "0"; } // 将字符串转换为整数 try { deviceInfo.power = Integer.parseInt(powerStr); } catch (NumberFormatException e) { Log.e(TAG, "解析电量信息失败: " + powerStr + ", 错误: " + e.getMessage()); deviceInfo.power = 0; } org.json.JSONObject data = new org.json.JSONObject(); data.put("ip", deviceInfo.ip); data.put("sn", deviceInfo.sn); data.put("power", deviceInfo.power); data.put("status", deviceInfo.status); data.put("playing", deviceInfo.playing); data.put("timestamp", System.currentTimeMillis()); return data.toString(); } catch (org.json.JSONException e) { Log.e(TAG, "序列化设备信息失败: " + e.getMessage()); return "{}"; } } /** * 断开 MQTT 连接 */ private void disconnectMqtt() { try { if (mqttScheduler != null) { mqttScheduler.shutdownNow(); Log.i(TAG, "MQTT 定时任务已停止"); } if (mqttClient != null && mqttClient.isConnected()) { mqttClient.disconnect(); mqttClient.close(); Log.i(TAG, "MQTT 连接已断开"); } } catch (MqttException e) { Log.e(TAG, "断开 MQTT 连接失败: " + e.getMessage()); } } }