可靠udp初步实现
This commit is contained in:
@@ -1,38 +1,59 @@
|
||||
package com.pinappletech.android.main;
|
||||
|
||||
import android.app.Service;
|
||||
import android.os.IBinder;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
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.util.Log;
|
||||
import android.os.Build;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
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 com.unity3d.player.UnityPlayer;
|
||||
|
||||
public class Main extends Service {
|
||||
private static final String TAG = "PineappleService";
|
||||
private Handler handler = new Handler(Looper.getMainLooper());
|
||||
private Runnable logRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i("PineappleService", "服务正在运行...");
|
||||
handler.postDelayed(this, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
// ================= 可靠UDP参数 =================
|
||||
private static final int MAX_RETRIES = 3;
|
||||
private static final int RETRY_INTERVAL = 200; // 重传间隔(ms)
|
||||
private static final int ACK_TIMEOUT = 300; // ACK超时(ms)
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
// 序列号计数器(线程安全)
|
||||
private int sequenceCounter = 0;
|
||||
private final Object sequenceLock = new Object();
|
||||
|
||||
// 待确认消息 <序列号, 消息数据>
|
||||
private final Map<Integer, PendingMessage> pendingMessages = new ConcurrentHashMap<>();
|
||||
|
||||
// 接收缓冲区 <序列号, 消息内容>
|
||||
private final Map<Integer, String> receiveBuffer = new ConcurrentHashMap<>();
|
||||
private int expectedSequence = 0; // 下一个期望序列号
|
||||
|
||||
// 线程池
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ExecutorService networkExecutor = Executors.newFixedThreadPool(2);
|
||||
|
||||
// ================= 服务生命周期 =================
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
@@ -40,8 +61,9 @@ public class Main extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.i("PineappleService", "菠萝服务启动");
|
||||
Log.i(TAG, "菠萝服务启动");
|
||||
|
||||
// 创建通知渠道
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
"pineapple_channel",
|
||||
@@ -63,18 +85,252 @@ public class Main extends Service {
|
||||
startForeground(1, notification);
|
||||
}
|
||||
|
||||
handler.post(logRunnable);
|
||||
// 启动UDP接收器
|
||||
startUdpReceiver();
|
||||
|
||||
// 启动重传检查任务
|
||||
scheduler.scheduleAtFixedRate(this::checkPendingMessages,
|
||||
RETRY_INTERVAL, RETRY_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.i("PineappleService", "菠萝服务停止");
|
||||
Log.i(TAG, "菠萝服务停止");
|
||||
|
||||
// 清理资源
|
||||
isRunning = false;
|
||||
scheduler.shutdownNow();
|
||||
networkExecutor.shutdownNow();
|
||||
if (udpSocket != null && !udpSocket.isClosed()) {
|
||||
udpSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 可靠UDP核心实现 =================
|
||||
private static class PendingMessage {
|
||||
byte[] data;
|
||||
InetAddress targetAddress;
|
||||
int targetPort;
|
||||
int retryCount;
|
||||
long lastSentTime;
|
||||
|
||||
PendingMessage(byte[] data, InetAddress targetAddress, int targetPort) {
|
||||
this.data = data;
|
||||
this.targetAddress = targetAddress;
|
||||
this.targetPort = targetPort;
|
||||
this.retryCount = 0;
|
||||
this.lastSentTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
// 重传检查
|
||||
private void checkPendingMessages() {
|
||||
long now = System.currentTimeMillis();
|
||||
pendingMessages.forEach((seq, msg) -> {
|
||||
if (now - msg.lastSentTime > RETRY_INTERVAL && msg.retryCount < MAX_RETRIES) {
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
DatagramPacket packet = new DatagramPacket(
|
||||
msg.data, msg.data.length, msg.targetAddress, msg.targetPort);
|
||||
udpSocket.send(packet);
|
||||
msg.retryCount++;
|
||||
msg.lastSentTime = now;
|
||||
Log.d(TAG, "重发消息 seq=" + seq + ", 重试次数=" + msg.retryCount);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "重发失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
} else if (msg.retryCount >= MAX_RETRIES) {
|
||||
pendingMessages.remove(seq);
|
||||
Log.w(TAG, "消息 seq=" + seq + " 达到最大重试次数");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理接收到的消息
|
||||
private void handleReceivedMessage(String message, InetAddress senderAddress, int senderPort) {
|
||||
// 解析消息头
|
||||
if (message.startsWith("MSG:") && message.length() > 5) {
|
||||
int colonIndex = message.indexOf(':', 4);
|
||||
if (colonIndex != -1) {
|
||||
try {
|
||||
int seq = Integer.parseInt(message.substring(4, colonIndex));
|
||||
String content = message.substring(colonIndex + 1);
|
||||
|
||||
// 处理ACK消息
|
||||
if (content.startsWith("ACK/")) {
|
||||
handleAck(seq);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理普通消息
|
||||
handleDataMessage(seq, content, senderAddress, senderPort);
|
||||
return;
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(TAG, "无效序列号: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 旧格式消息处理(兼容)
|
||||
Log.i(TAG, "收到旧格式消息: " + message);
|
||||
handleLegacyMessage(message, senderAddress, senderPort);
|
||||
}
|
||||
|
||||
private void handleAck(int ackSeq) {
|
||||
// 从待确认字典中移除
|
||||
pendingMessages.remove(ackSeq);
|
||||
Log.d(TAG, "收到ACK确认 seq=" + ackSeq);
|
||||
}
|
||||
|
||||
private void handleDataMessage(int seq, String content, InetAddress senderAddress, int senderPort) {
|
||||
// 发送ACK确认
|
||||
sendAck(seq, senderAddress, senderPort);
|
||||
|
||||
// 处理乱序消息
|
||||
receiveBuffer.put(seq, content);
|
||||
|
||||
// 按顺序处理缓冲的消息
|
||||
while (receiveBuffer.containsKey(expectedSequence)) {
|
||||
String bufferedMsg = receiveBuffer.remove(expectedSequence);
|
||||
processValidMessage(bufferedMsg, senderAddress);
|
||||
expectedSequence++;
|
||||
}
|
||||
}
|
||||
|
||||
private void processValidMessage(String message, InetAddress senderAddress) {
|
||||
// 实际业务处理
|
||||
Log.i(TAG, "收到有效消息: " + message);
|
||||
|
||||
// 这里添加你的业务逻辑...
|
||||
if ("PLAY".equals(message)) {
|
||||
Log.i(TAG, "执行播放指令");
|
||||
callUnity("AppManager", "UnityMethod", "play");
|
||||
} else if ("STOP".equals(message)) {
|
||||
Log.i(TAG, "执行停止指令");
|
||||
callUnity("AppManager", "UnityMethod", "stop");
|
||||
}
|
||||
// 其他指令...
|
||||
}
|
||||
|
||||
private void sendAck(int seq, InetAddress targetAddress, int targetPort) {
|
||||
String ackMsg = "MSG:" + seq + ":ACK/" + seq;
|
||||
byte[] ackData = ackMsg.getBytes();
|
||||
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
DatagramPacket packet = new DatagramPacket(
|
||||
ackData, ackData.length, targetAddress, targetPort);
|
||||
udpSocket.send(packet);
|
||||
Log.d(TAG, "发送ACK seq=" + seq);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "发送ACK失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 可靠发送方法
|
||||
private void sendReliable(InetAddress targetAddress, int targetPort, String message) {
|
||||
int seq;
|
||||
synchronized (sequenceLock) {
|
||||
seq = sequenceCounter++;
|
||||
}
|
||||
|
||||
// 构建可靠消息
|
||||
String formattedMsg = "MSG:" + seq + ":" + message;
|
||||
byte[] data = formattedMsg.getBytes();
|
||||
|
||||
// 添加到待确认字典
|
||||
PendingMessage pendingMsg = new PendingMessage(data, targetAddress, targetPort);
|
||||
pendingMessages.put(seq, pendingMsg);
|
||||
|
||||
// 初始发送
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
DatagramPacket packet = new DatagramPacket(
|
||||
data, data.length, targetAddress, targetPort);
|
||||
udpSocket.send(packet);
|
||||
Log.d(TAG, "发送可靠消息 seq=" + seq + ", 内容=" + message);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "发送失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ================= 旧版兼容处理 =================
|
||||
private void handleLegacyMessage(String message, InetAddress senderAddress, int senderPort) {
|
||||
if ("DISCOVER".equals(message)) {
|
||||
String sn = Main.sn;
|
||||
String power = Main.power;
|
||||
String jsonResponse = String.format("{\"SN\":\"%s\",\"Power\":\"%s\"}", sn, power);
|
||||
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
byte[] response = jsonResponse.getBytes();
|
||||
DatagramPacket responsePacket = new DatagramPacket(
|
||||
response, response.length, senderAddress, senderPort);
|
||||
udpSocket.send(responsePacket);
|
||||
Log.i(TAG, "发送DISCOVER响应");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "发送DISCOVER响应失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ================= UDP接收线程 =================
|
||||
private DatagramSocket udpSocket;
|
||||
private boolean isRunning = false;
|
||||
|
||||
private void startUdpReceiver() {
|
||||
Log.i(TAG, "启动UDP接收器");
|
||||
isRunning = true;
|
||||
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
udpSocket = new DatagramSocket(8888);
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
while (isRunning) {
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
udpSocket.receive(packet);
|
||||
|
||||
String receivedData = new String(
|
||||
packet.getData(), 0, packet.getLength()).trim();
|
||||
|
||||
Log.i(TAG, "原始UDP数据: " + receivedData);
|
||||
handleReceivedMessage(
|
||||
receivedData,
|
||||
packet.getAddress(),
|
||||
packet.getPort());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (isRunning) {
|
||||
Log.e(TAG, "UDP接收错误: " + e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
if (udpSocket != null && !udpSocket.isClosed()) {
|
||||
udpSocket.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ================= 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 {
|
||||
@@ -92,120 +348,14 @@ public class Main extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private DatagramSocket udpSocket;
|
||||
private boolean isRunning = false;
|
||||
private Thread udpReceiveThread;
|
||||
|
||||
private void startUdpReceiver() {
|
||||
Log.i("PineappleService", "启动UDP接收");
|
||||
isRunning = true;
|
||||
udpReceiveThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
udpSocket = new DatagramSocket(8888);
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
while (isRunning) {
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
udpSocket.receive(packet);
|
||||
|
||||
String receivedData = new String(packet.getData(), 0, packet.getLength()).trim();
|
||||
Log.i("PineappleService", "收到UDP数据: " + receivedData);
|
||||
|
||||
if ("DISCOVER".equals(receivedData)) {
|
||||
String sn = Main.sn;
|
||||
String power = Main.power;
|
||||
|
||||
String jsonResponse = String.format("{\"SN\":\"%s\",\"Power\":\"%s\"}", sn, power);
|
||||
|
||||
byte[] response = jsonResponse.getBytes();
|
||||
DatagramPacket responsePacket = new DatagramPacket(
|
||||
response,
|
||||
response.length,
|
||||
packet.getAddress(),
|
||||
packet.getPort());
|
||||
udpSocket.send(responsePacket);
|
||||
|
||||
} else if ("PLAY".equals(receivedData)) {
|
||||
Log.i("PineappleService", "收到播放指令");
|
||||
// callUnityMethod("AppManager", "StartApp", null);
|
||||
// UnityPlayer.UnitySendMessage("AppManager", "UnityMethod", "test");
|
||||
callUnity("AppManager", "UnityMethod", "test");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("PineappleService", "UDP接收错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
udpReceiveThread.start();
|
||||
}
|
||||
|
||||
// 用于接收 Unity 返回的值
|
||||
private static String sn = "未知";
|
||||
private static String power = "0%";
|
||||
|
||||
// Unity 调用此方法设置 SN
|
||||
public static void setSnFromUnity(String sn) {
|
||||
Main.sn = sn;
|
||||
}
|
||||
|
||||
// Unity 调用此方法设置电量
|
||||
public static void setPowerFromUnity(String power) {
|
||||
Main.power = power;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 调用Unity的方法
|
||||
// *
|
||||
// * @param gameObjectName 调用的GameObject的名称
|
||||
// * @param functionName 方法名
|
||||
// * @param args 参数
|
||||
// * @return 调用是否成功
|
||||
// */
|
||||
// boolean callUnity(String gameObjectName, String functionName, String args) {
|
||||
// final String TAG = "PineappleService"; // 日志标签
|
||||
// try {
|
||||
// // 1. 获取UnityPlayer类
|
||||
// Class<?> classtype = Class.forName("com.unity3d.player.UnityPlayer");
|
||||
|
||||
// // 2. 获取UnitySendMessage静态方法
|
||||
// Method method = classtype.getMethod("UnitySendMessage", String.class,
|
||||
// String.class, String.class);
|
||||
|
||||
// // 3. 关键修复:静态方法第一个参数必须为null
|
||||
// method.invoke(null, gameObjectName, functionName, args); // ✅ 修复静态调用
|
||||
// return true;
|
||||
|
||||
// } catch (ClassNotFoundException e) {
|
||||
// Log.i(TAG, " ERROR: UnityPlayer class not found! Check Unity integration.");
|
||||
// } catch (NoSuchMethodException e) {
|
||||
// Log.i(TAG, " ERROR: UnitySendMessage method not found! Update Unity
|
||||
// library.");
|
||||
// } catch (IllegalAccessException e) {
|
||||
// Log.i(TAG, " ERROR: Access denied to UnitySendMessage! Check method
|
||||
// visibility.");
|
||||
// } catch (InvocationTargetException e) {
|
||||
// Log.i(TAG, " ERROR: UnitySendMessage crashed: " + e.getTargetException());
|
||||
// } catch (Exception e) {
|
||||
// Log.i(TAG, " UNEXPECTED ERROR: " + e.getMessage());
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
public void callUnity(String gameObjectName, String methodName, String message) {
|
||||
try {
|
||||
// 使用UnityPlayer.currentActivity获取当前Activity上下文
|
||||
UnityPlayer.currentActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 直接使用UnityPlayer的静态方法UnitySendMessage
|
||||
UnityPlayer.UnitySendMessage(gameObjectName, methodName, message);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e("PineappleService", "调用Unity方法失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user