Files
MagicCaptainService/Assets/Plugins/Android/Main.java
2026-04-16 17:49:48 +08:00

1044 lines
40 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String, String> packageNamesMap = new HashMap<>();
private List<String> 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<android.content.pm.PackageInfo> 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<String, String> 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<String, String> 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());
}
}
}