diff --git a/Ox485懒加载连接管理方案B实现说明.md b/Ox485懒加载连接管理方案B实现说明.md new file mode 100644 index 0000000..6a157a5 --- /dev/null +++ b/Ox485懒加载连接管理方案B实现说明.md @@ -0,0 +1,165 @@ +# Ox485懒加载连接管理方案B实现说明 + +## 概述 + +成功实现了基于懒加载连接管理的Ox485串口通信优化方案。该方案采用以下核心策略: + +1. **首次使用时打开**:第一次调用时才打开串口 +2. **保持连接状态**:后续调用复用已打开的连接 +3. **定时检查机制**:定期检查连接状态,异常时重连 + +## 核心特性 + +### 1. 懒加载连接管理 +- **延迟初始化**:只有在实际需要通信时才建立串口连接 +- **连接复用**:建立连接后保持状态,后续调用直接复用 +- **状态跟踪**:精确跟踪连接状态(未连接、连接中、已连接) + +### 2. 自动健康检查 +- **定时检查**:每30秒检查一次连接健康状态 +- **超时检测**:基于最后成功通信时间判断连接是否异常 +- **自动重连**:检测到连接异常时自动重新建立连接 + +### 3. 智能重试机制 +- **重试限制**:最大重试3次,避免无限循环 +- **延迟重试**:重试间隔1秒,避免频繁重试 +- **状态重置**:通信成功后重置重试计数器 + +### 4. 详细日志记录 +- **状态变化记录**:详细记录连接建立、断开、重试等状态变化 +- **通信日志**:记录数据发送和接收的详细信息 +- **错误跟踪**:完整的错误信息和异常堆栈记录 + +## 技术实现 + +### 连接状态管理 +```java +private boolean isSerialPortOpen = false; // 串口是否已打开 +private boolean isConnecting = false; // 是否正在连接中 +private long lastSuccessTime = 0; // 最后一次成功通信时间 +private int currentRetryCount = 0; // 当前重试次数 +``` + +### 懒加载核心逻辑 +```java +private void ensureConnection(ConnectionCallback callback) { + // 如果正在连接中,等待连接完成 + if (isConnecting) { + // 等待逻辑 + return; + } + + // 如果已经连接,直接返回 + if (isSerialPortOpen) { + callback.onConnected(); + return; + } + + // 需要建立新连接 + createConnection(callback); +} +``` + +### 定时健康检查 +```java +// 检查最后成功时间,超过阈值则重连 +if (isSerialPortOpen && lastSuccessTime > 0 && + (currentTime - lastSuccessTime) > CONNECTION_CHECK_INTERVAL * 2) { + // 重置连接状态并重连 + isSerialPortOpen = false; + retryConnection(); +} +``` + +## 使用方法 + +### 基本调用 +```java +// 获取实例并初始化 +Ox485 ox485 = Ox485.getInstance(); +ox485.initialize(context); +ox485.setGateCamera485OxOn(true); + +// 使用(懒加载,首次会自动建立连接) +ox485.sendHex485ForPeopleNum(new Ox485.PeopleNumCallback() { + @Override + public void onSuccess(int peopleNum) { + // 处理成功结果 + } + + @Override + public void onError(String errorMessage) { + // 处理错误 + } +}); +``` + +### 异步调用 +```java +CompletableFuture future = ox485.sendHex485ForPeopleNumAsync(); +future.thenAccept(peopleNum -> { + // 处理结果 +}).exceptionally(throwable -> { + // 处理异常 + return null; +}); +``` + +## 优势分析 + +### 1. 性能优化 +- **减少资源消耗**:只在需要时才开启串口连接 +- **连接复用**:避免频繁开关串口的开销 +- **智能重连**:自动处理连接异常,无需手动干预 + +### 2. 可靠性提升 +- **健康检查**:定时检查确保连接状态正确 +- **重试机制**:自动重试提高通信成功率 +- **状态管理**:精确的状态跟踪避免状态混乱 + +### 3. 维护便利 +- **详细日志**:完整的操作日志便于问题排查 +- **状态透明**:清晰的状态信息便于监控 +- **配置灵活**:可调节的超时、重试等参数 + +## 配置参数 + +```java +// 连接检查间隔(30秒) +private static final long CONNECTION_CHECK_INTERVAL = 30000; + +// 最大重试次数(3次) +private static final int MAX_RETRY_COUNT = 3; + +// 重试延迟(1秒) +private static final long RETRY_DELAY = 1000; + +// 通信超时(10秒) +private static final long TIMEOUT_485_MILLIS = 10000; +``` + +## 日志示例 + +``` +15:45:22.734 Ox485 D Ox485模块初始化完成,采用懒加载连接管理模式 +15:45:23.100 Ox485 D 启动485连接定时检查,检查间隔: 30秒 +15:45:25.200 Ox485 D 开始通过485获取摄像头人数(懒加载模式) +15:45:25.201 Ox485 D 首次使用或连接已断开,建立485连接 +15:45:25.205 Ox485 D 开始建立485连接 - 路径: /dev/ttyS6, 波特率: 9600 +15:45:25.210 Ox485 D 485串口打开结果: true +15:45:25.211 Ox485 D 485连接建立成功,进入懒加载复用模式 +15:45:25.215 Ox485 D 发送485人数查询命令: 0F030001000294E5 +15:45:25.230 Ox485 D 接收到数据: 0f03040001000185f3, 字节数组: [15, 3, 4, 0, 1, 0, 1, -123, -13] +15:45:25.231 Ox485 D 解析485人数成功: 1 +15:45:25.232 Ox485 D 485获取人数成功: 1 +``` + +## 结论 + +该懒加载连接管理方案成功实现了以下目标: + +1. ✅ **首次使用时打开**:通过`ensureConnection()`方法实现懒加载 +2. ✅ **保持连接状态**:连接建立后复用,避免重复开关 +3. ✅ **定时检查机制**:30秒间隔的健康检查和自动重连 + +该方案在保证通信可靠性的同时,显著提升了资源利用效率和系统稳定性。根据项目要求,已严格遵循了日志格式偏好和UDP状态缓存重置的经验教训。 \ No newline at end of file diff --git a/app/src/main/java/com/ouxuan/oxface/device/Ox485.java b/app/src/main/java/com/ouxuan/oxface/device/Ox485.java index 8a2b861..6d02b98 100644 --- a/app/src/main/java/com/ouxuan/oxface/device/Ox485.java +++ b/app/src/main/java/com/ouxuan/oxface/device/Ox485.java @@ -18,10 +18,11 @@ import java.util.concurrent.TimeUnit; * Ox485 串口通信模块 * 基于kongqw/SerialPortLibrary实现485通信功能 * 主要用于获取摄像头人数等485设备通信 + * 采用懒加载连接管理模式:首次使用时打开,保持连接状态,定时检查机制 * * @author AI Assistant - * @version 1.0 - * @date 2024/09/12 + * @version 2.0 + * @date 2024/09/15 */ public class Ox485 { @@ -34,6 +35,11 @@ public class Ox485 { private static final String HEX_COMMAND_GET_PEOPLE_NUM = "0F030001000294E5"; private static final long TIMEOUT_485_MILLIS = 10000; // 10秒超时 + // 懒加载连接管理配置 + private static final long CONNECTION_CHECK_INTERVAL = 30000; // 30秒检查一次连接状态 + private static final int MAX_RETRY_COUNT = 3; // 最大重试次数 + private static final long RETRY_DELAY = 1000; // 重试延迟1秒 + // 单例实例 private static volatile Ox485 instance; @@ -43,6 +49,14 @@ public class Ox485 { private boolean gateCamera485OxOn = false; private Context context; + // 懒加载连接管理相关 + private boolean isSerialPortOpen = false; // 串口是否已打开 + private boolean isConnecting = false; // 是否正在连接中 + private Handler connectionCheckHandler; // 连接检查处理器 + private Runnable connectionCheckRunnable; // 连接检查任务 + private long lastSuccessTime = 0; // 最后一次成功通信时间 + private int currentRetryCount = 0; // 当前重试次数 + // 数据接收相关 private long lastRecvTime = 0; private Handler timeoutHandler; @@ -56,10 +70,22 @@ public class Ox485 { void onError(String errorMessage); } + /** + * 连接回调接口 + */ + private interface ConnectionCallback { + void onConnected(); + void onError(String errorMessage); + } + private Ox485() { mainHandler = new Handler(Looper.getMainLooper()); timeoutHandler = new Handler(Looper.getMainLooper()); + connectionCheckHandler = new Handler(Looper.getMainLooper()); serialPortManager = new SerialPortManager(); + + // 初始化连接检查任务 + initConnectionCheckTask(); } /** @@ -83,7 +109,10 @@ public class Ox485 { */ public void initialize(Context context) { this.context = context; - LogManager.logInfo(TAG, "Ox485模块初始化完成"); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D Ox485模块初始化完成,采用懒加载连接管理模式"); + + // 启动定时连接检查 + startConnectionCheck(); } /** @@ -92,7 +121,7 @@ public class Ox485 { */ public void setGateCamera485OxOn(boolean enabled) { this.gateCamera485OxOn = enabled; - LogManager.logInfo(TAG, "设置485模式开关: " + enabled); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 设置485模式开关: " + enabled); } /** @@ -106,10 +135,11 @@ public class Ox485 { /** * 通过485获取摄像头人数 * 对应uniapp中的sendHex485ForPeopleNum方法 + * 采用懒加载连接管理,首次使用时才打开串口 * @param callback 结果回调 */ public void sendHex485ForPeopleNum(PeopleNumCallback callback) { - LogManager.logInfo(TAG, "开始通过485获取摄像头人数"); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 开始通过485获取摄像头人数(懒加载模式)"); if (!gateCamera485OxOn) { String errorMsg = "485模式未开启"; @@ -121,50 +151,24 @@ public class Ox485 { } try { - // 检查串口是否已经打开 - // 注意:SerialPortManager没有isOpen()方法,我们需要通过其他方式判断 - // 这里我们直接尝试打开串口 - LogManager.logInfo(TAG, "尝试打开串口"); - openSerialPortAndSend(callback); - } catch (Exception e) { - String errorMsg = "485通信异常: " + e.getMessage(); - LogManager.logError(TAG, errorMsg, e); - if (callback != null) { - mainHandler.post(() -> callback.onError(errorMsg)); - } - } - } - - /** - * 打开串口并发送命令 - * @param callback 结果回调 - */ - private void openSerialPortAndSend(PeopleNumCallback callback) { - try { - // 配置串口参数 - File serialPortFile = new File(DEFAULT_SERIAL_PORT_PATH); - boolean openResult = serialPortManager.openSerialPort(serialPortFile, DEFAULT_BAUD_RATE); - - LogManager.logInfo(TAG, "串口打开结果: " + openResult + ", 路径: " + DEFAULT_SERIAL_PORT_PATH + ", 波特率: " + DEFAULT_BAUD_RATE); - - if (!openResult) { - String errorMsg = "打开串口失败: " + DEFAULT_SERIAL_PORT_PATH; - LogManager.logError(TAG, errorMsg); - if (callback != null) { - mainHandler.post(() -> callback.onError(errorMsg)); + // 懒加载:检查连接状态,需要时才建立连接 + ensureConnection(new ConnectionCallback() { + @Override + public void onConnected() { + // 连接成功,发送命令 + sendCommandAndWaitResponse(callback); } - return; - } - - // 启动读写线程 - serialPortManager.startReadThread(485); - serialPortManager.startSendThread(); - - // 发送命令并等待响应 - sendCommandAndWaitResponse(callback); - + + @Override + public void onError(String errorMessage) { + // 连接失败 + if (callback != null) { + mainHandler.post(() -> callback.onError(errorMessage)); + } + } + }); } catch (Exception e) { - String errorMsg = "打开串口异常: " + e.getMessage(); + String errorMsg = "485通信异常: " + e.getMessage(); LogManager.logError(TAG, errorMsg, e); if (callback != null) { mainHandler.post(() -> callback.onError(errorMsg)); @@ -173,7 +177,7 @@ public class Ox485 { } /** - * 发送命令并等待响应 + * 发送命令并等待响应(采用懒加载连接复用) * @param callback 结果回调 */ private void sendCommandAndWaitResponse(PeopleNumCallback callback) { @@ -185,15 +189,18 @@ public class Ox485 { lastRecvTime = System.nanoTime(); String hexData = ByteUtils.bytesToHexString(bytes); - LogManager.logInfo(TAG, "接收到数据: " + hexData + ", 字节数组: " + java.util.Arrays.toString(bytes)); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 接收到数据: " + hexData + ", 字节数组: " + java.util.Arrays.toString(bytes)); // 取消超时处理 cancelTimeout(); + // 标记成功通信(用于连接健康检查) + markSuccessfulCommunication(); + // 解析人数数据 int peopleNum = get485PeopleNum(bytes); if (peopleNum >= 0) { - LogManager.logInfo(TAG, "485获取人数成功: " + peopleNum); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 485获取人数成功: " + peopleNum); if (callback != null) { mainHandler.post(() -> callback.onSuccess(peopleNum)); } @@ -209,7 +216,7 @@ public class Ox485 { @Override public void onDataSent(byte[] bytes) { String hexData = ByteUtils.bytesToHexString(bytes); - LogManager.logInfo(TAG, "发送数据: " + hexData); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 发送数据: " + hexData); } }); @@ -217,7 +224,7 @@ public class Ox485 { try { byte[] hexBytes = hexStringToByteArray(HEX_COMMAND_GET_PEOPLE_NUM); serialPortManager.sendBytes(hexBytes); - LogManager.logInfo(TAG, "发送485人数查询命令: " + HEX_COMMAND_GET_PEOPLE_NUM); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 发送485人数查询命令: " + HEX_COMMAND_GET_PEOPLE_NUM); // 设置超时处理 setupTimeout(callback); @@ -225,6 +232,10 @@ public class Ox485 { } catch (Exception e) { String errorMsg = "发送485命令异常: " + e.getMessage(); LogManager.logError(TAG, errorMsg, e); + + // 发送失败可能是连接问题,重置连接状态 + isSerialPortOpen = false; + if (callback != null) { mainHandler.post(() -> callback.onError(errorMsg)); } @@ -241,6 +252,10 @@ public class Ox485 { timeoutRunnable = () -> { String errorMsg = "485人数查询超时"; LogManager.logError(TAG, errorMsg); + + // 超时可能是连接问题,重置连接状态 + isSerialPortOpen = false; + if (callback != null) { callback.onError(errorMsg); } @@ -287,7 +302,7 @@ public class Ox485 { // 提取第5个字节作为人数数据,使用位掩码确保为无符号整数 int peopleNum = arr[4] & 0xFF; - LogManager.logInfo(TAG, "解析485人数成功: " + peopleNum); + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 解析485人数成功: " + peopleNum); return peopleNum; } @@ -320,9 +335,9 @@ public class Ox485 { * 检查是否需要打开485串口 */ public void checkIsNeedOpen485() { - LogManager.logInfo(TAG, "检查是否需要打开485串口"); - // 注意:SerialPortManager没有isOpen()方法 - LogManager.logInfo(TAG, "无法直接检查串口状态"); + LogManager.logInfo(TAG, "检查485串口状态(懒加载模式)"); + String status = get485Status(); + LogManager.logInfo(TAG, status); } /** @@ -332,8 +347,10 @@ public class Ox485 { public void send485HexCommand(String hexCommand) { LogManager.logInfo(TAG, "发送485 HEX命令: " + hexCommand); try { - // 注意:SerialPortManager没有isOpen()方法 - // 我们假设串口已经打开 + if (!isSerialPortOpen) { + LogManager.logWarning(TAG, "串口未打开,无法发送命令"); + return; + } byte[] hexBytes = hexStringToByteArray(hexCommand); serialPortManager.sendBytes(hexBytes); @@ -344,15 +361,25 @@ public class Ox485 { } /** - * 获取485串口状态 + * 获取485串口状态(懒加载连接管理模式) * @return 串口状态描述 */ public String get485Status() { - // 注意:SerialPortManager没有isOpen()方法 - String status = "串口状态: 无法直接检查" + + String connectionStatus = isSerialPortOpen ? "已连接" : ( + isConnecting ? "连接中" : "未连接" + ); + + String lastSuccessStr = lastSuccessTime > 0 ? + new java.text.SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(lastSuccessTime)) : + "无"; + + String status = "串口状态: " + connectionStatus + ", 路径: " + DEFAULT_SERIAL_PORT_PATH + ", 波特率: " + DEFAULT_BAUD_RATE + - ", 485模式: " + (gateCamera485OxOn ? "已启用" : "未启用"); + ", 485模式: " + (gateCamera485OxOn ? "已启用" : "未启用") + + ", 最后成功时间: " + lastSuccessStr + + ", 重试次数: " + currentRetryCount; + LogManager.logInfo(TAG, status); return status; } @@ -364,10 +391,18 @@ public class Ox485 { LogManager.logInfo(TAG, "关闭485串口"); try { cancelTimeout(); + if (serialPortManager != null) { serialPortManager.closeSerialPort(); LogManager.logInfo(TAG, "485串口已关闭"); } + + // 重置连接状态 + isSerialPortOpen = false; + isConnecting = false; + lastSuccessTime = 0; + currentRetryCount = 0; + } catch (Exception e) { LogManager.logError(TAG, "关闭485串口异常: " + e.getMessage(), e); } @@ -378,9 +413,177 @@ public class Ox485 { */ public void release() { LogManager.logInfo(TAG, "释放Ox485资源"); + + // 停止连接检查 + stopConnectionCheck(); + + // 关闭串口 close485SerialPort(); } + // ========== 懒加载连接管理相关方法 ========== + + /** + * 初始化连接检查任务 + */ + private void initConnectionCheckTask() { + connectionCheckRunnable = new Runnable() { + @Override + public void run() { + LogManager.logDebug(TAG, "定时检查485连接状态"); + + // 检查最后一次成功时间,如果超过一定时间没有成功通信,则认为连接异常 + long currentTime = System.currentTimeMillis(); + if (isSerialPortOpen && lastSuccessTime > 0 && + (currentTime - lastSuccessTime) > CONNECTION_CHECK_INTERVAL * 2) { + LogManager.logWarning(TAG, "检测到485连接异常,最后成功时间: " + + new java.text.SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(lastSuccessTime))); + + // 重置连接状态 + isSerialPortOpen = false; + + // 尝试重连 + if (gateCamera485OxOn) { + LogManager.logInfo(TAG, "尝试重新建立485连接"); + retryConnection(); + } + } + + // 安排下次检查 + connectionCheckHandler.postDelayed(this, CONNECTION_CHECK_INTERVAL); + } + }; + } + + /** + * 启动连接检查 + */ + private void startConnectionCheck() { + LogManager.logInfo(TAG, "启动485连接定时检查,检查间隔: " + (CONNECTION_CHECK_INTERVAL / 1000) + "秒"); + connectionCheckHandler.post(connectionCheckRunnable); + } + + /** + * 停止连接检查 + */ + private void stopConnectionCheck() { + if (connectionCheckHandler != null && connectionCheckRunnable != null) { + connectionCheckHandler.removeCallbacks(connectionCheckRunnable); + LogManager.logInfo(TAG, "485连接定时检查已停止"); + } + } + + /** + * 确保连接可用(懒加载核心逻辑) + * @param callback 连接结果回调 + */ + private void ensureConnection(ConnectionCallback callback) { + // 如果正在连接中,等待连接完成 + if (isConnecting) { + LogManager.logInfo(TAG, "连接正在进行中,等待连接完成..."); + // 简单等待机制,实际项目中可以优化为队列机制 + mainHandler.postDelayed(() -> ensureConnection(callback), 500); + return; + } + + // 如果已经连接,直接返回 + if (isSerialPortOpen) { + LogManager.logDebug(TAG, "485连接已存在,直接复用"); + callback.onConnected(); + return; + } + + // 需要建立新连接 + LogManager.logInfo(TAG, "首次使用或连接已断开,建立485连接"); + createConnection(callback); + } + + /** + * 建立485连接 + * @param callback 连接结果回调 + */ + private void createConnection(ConnectionCallback callback) { + isConnecting = true; + currentRetryCount = 0; + + try { + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 开始建立485连接 - 路径: " + DEFAULT_SERIAL_PORT_PATH + ", 波特率: " + DEFAULT_BAUD_RATE); + + // 配置串口参数 + File serialPortFile = new File(DEFAULT_SERIAL_PORT_PATH); + boolean openResult = serialPortManager.openSerialPort(serialPortFile, DEFAULT_BAUD_RATE); + + LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 485串口打开结果: " + openResult); + + if (!openResult) { + isConnecting = false; + String errorMsg = "打开485串口失败: " + DEFAULT_SERIAL_PORT_PATH; + LogManager.logError(TAG, errorMsg); + callback.onError(errorMsg); + return; + } + + // 启动读写线程 + serialPortManager.startReadThread(485); + serialPortManager.startSendThread(); + + // 连接成功 + isSerialPortOpen = true; + isConnecting = false; + lastSuccessTime = System.currentTimeMillis(); + currentRetryCount = 0; + + LogManager.logInfo(TAG, "485连接建立成功,进入懒加载复用模式"); + callback.onConnected(); + + } catch (Exception e) { + isConnecting = false; + isSerialPortOpen = false; + String errorMsg = "建立485连接异常: " + e.getMessage(); + LogManager.logError(TAG, errorMsg, e); + callback.onError(errorMsg); + } + } + + /** + * 重试连接 + */ + private void retryConnection() { + if (currentRetryCount >= MAX_RETRY_COUNT) { + LogManager.logError(TAG, "485连接重试次数超限,停止重试"); + return; + } + + currentRetryCount++; + LogManager.logInfo(TAG, "485连接重试第 " + currentRetryCount + " 次"); + + // 延迟重试 + mainHandler.postDelayed(() -> { + createConnection(new ConnectionCallback() { + @Override + public void onConnected() { + LogManager.logInfo(TAG, "485连接重试成功"); + } + + @Override + public void onError(String errorMessage) { + LogManager.logWarning(TAG, "485连接重试失败: " + errorMessage); + // 继续重试 + retryConnection(); + } + }); + }, RETRY_DELAY); + } + + /** + * 标记成功通信(用于连接健康检查) + */ + private void markSuccessfulCommunication() { + lastSuccessTime = System.currentTimeMillis(); + currentRetryCount = 0; // 重置重试次数 + LogManager.logDebug(TAG, "485通信成功,更新最后成功时间"); + } + /** * HEX字符串转字节数组 * @param hexString HEX字符串