1. 环境准备与项目创建在开始构建串口通信应用之前我们需要确保开发环境已经准备就绪。首先打开Android Studio点击Start a new Android Studio project创建一个新项目。这里建议选择Empty Activity模板因为我们需要从头开始构建串口通信功能。项目创建完成后我们需要在build.gradle文件中添加必要的依赖。目前最常用的串口通信库是android-serialport-api我们可以通过以下方式添加依赖dependencies { implementation com.licheedev:android-serialport:2.1.3 }如果你遇到无法找到库的问题可能需要添加jcenter或mavenCentral仓库allprojects { repositories { jcenter() mavenCentral() } }在实际项目中我遇到过因为网络问题导致依赖下载失败的情况。这时候可以尝试切换网络环境或者直接在本地缓存中查找是否已经下载过该依赖。另外建议在添加依赖后立即执行一次Gradle同步确保依赖能够正常加载。2. 串口通信基础配置2.1 权限配置串口通信需要访问设备的硬件接口因此我们需要在AndroidManifest.xml中添加必要的权限声明uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE/ uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE/虽然从Android 6.0开始需要运行时权限申请但串口通信本身不需要特殊的运行时权限。不过在实际开发中我建议还是添加存储权限因为很多时候我们需要将接收到的数据保存到文件中进行后续分析。2.2 串口参数设置串口通信有几个关键参数需要配置包括波特率、数据位、停止位和校验位。这些参数必须与连接的设备保持一致否则通信将无法正常进行。我们可以创建一个配置类来管理这些参数public class SerialConfig { private String port; // 串口设备路径如/dev/ttyS1 private int baudRate; // 波特率如9600 private int dataBits; // 数据位通常为8 private int stopBits; // 停止位通常为1 private int parity; // 校验位0表示无校验 private int flowControl; // 流控0表示无流控 // 构造函数和getter/setter方法 }在实际项目中我发现波特率是最容易出错的参数。曾经有一次调试花了整整一天时间最后发现是因为设备端和应用的波特率设置不一致。建议在开发初期就将这些参数提取为可配置项方便后续调试。3. 串口通信实现3.1 打开串口使用android-serialport-api打开串口非常简单SerialPort serialPort new SerialPort( new File(/dev/ttyS1), // 串口设备文件 9600, // 波特率 0, // 校验位 8, // 数据位 1 // 停止位 );打开串口后我们可以获取输入输出流InputStream inputStream serialPort.getInputStream(); OutputStream outputStream serialPort.getOutputStream();这里有个坑需要注意不同设备的串口设备路径可能不同。常见的路径包括/dev/ttyS0、/dev/ttyS1等。在实际项目中我通常会先列出设备的所有串口然后让用户选择正确的端口。3.2 数据收发处理串口通信的核心就是数据的发送和接收。发送数据相对简单byte[] sendData Hello Serial.getBytes(); outputStream.write(sendData); outputStream.flush();接收数据则需要开启一个单独的线程因为读取操作是阻塞的new Thread(() - { byte[] buffer new byte[1024]; int size; while (true) { try { size inputStream.read(buffer); if (size 0) { byte[] receivedData new byte[size]; System.arraycopy(buffer, 0, receivedData, 0, size); // 处理接收到的数据 } } catch (IOException e) { e.printStackTrace(); break; } } }).start();在实际项目中我发现数据接收的稳定性是个大问题。特别是当数据量较大时可能会出现数据丢失或粘包的情况。我的解决方案是设计一个简单的协议比如在数据包前后添加特定的起始和结束标志或者实现一个简单的校验机制。4. 错误处理与调试技巧4.1 常见错误排查串口通信开发中最常见的错误包括权限不足确保应用有访问串口设备的权限参数不匹配检查波特率、数据位等参数是否与设备端一致设备路径错误确认使用的串口设备路径是否正确我曾经遇到过一个奇怪的问题在某个设备上串口能正常通信换到另一个相同型号的设备就不行了。经过排查发现是因为两个设备的串口设备路径不同一个是/dev/ttyS1另一个是/dev/ttyS2。4.2 调试技巧调试串口通信时我通常会采用以下方法使用串口调试助手验证硬件是否正常工作在代码中添加详细的日志记录发送和接收的原始数据实现数据回显功能将接收到的数据原样发送回去验证通信链路// 简单的数据回显实现 byte[] buffer new byte[1024]; int size inputStream.read(buffer); if (size 0) { outputStream.write(buffer, 0, size); outputStream.flush(); }另外建议在应用中加入串口参数的可视化配置界面。这样在调试时可以快速修改参数而不用重新编译应用。我在一个工业项目中就实现了这样的功能大大提高了现场调试的效率。5. 性能优化与稳定性提升5.1 数据缓冲处理当通信数据量较大时简单的读取方式可能会导致数据丢失。我通常会实现一个环形缓冲区来暂存接收到的数据public class SerialBuffer { private byte[] buffer; private int head; private int tail; public SerialBuffer(int size) { buffer new byte[size]; head 0; tail 0; } public synchronized void put(byte[] data) { // 实现数据存入逻辑 } public synchronized byte[] get(int length) { // 实现数据取出逻辑 return null; } }这种设计可以有效避免数据丢失特别是在数据接收速度大于处理速度的情况下。在实际项目中我将缓冲区大小设置为接收数据最大包的3倍这样即使偶尔出现数据处理延迟也不会导致数据丢失。5.2 心跳机制为了保证通信的稳定性我通常会实现一个简单的心跳机制。定期发送心跳包如果长时间没有收到响应就认为连接出现了问题private void startHeartbeat() { new Timer().schedule(new TimerTask() { Override public void run() { if (lastReceiveTime TIMEOUT System.currentTimeMillis()) { // 超时处理 reconnect(); } else { // 发送心跳包 sendHeartbeat(); } } }, 0, HEARTBEAT_INTERVAL); }在工业环境中网络条件可能不太稳定这种心跳机制可以大大提高通信的可靠性。我曾经在一个项目中实现了这个机制将通信失败率从15%降到了不到1%。6. 实际应用案例6.1 与PLC通信在与PLC通信的项目中我发现Modbus RTU协议是常用的通信协议。虽然android-serialport-api不直接支持Modbus但我们可以基于它实现Modbus通信public byte[] readHoldingRegisters(int slaveId, int startAddr, int quantity) { byte[] request new byte[8]; request[0] (byte) slaveId; request[1] 0x03; // 功能码 request[2] (byte) (startAddr 8); request[3] (byte) startAddr; request[4] (byte) (quantity 8); request[5] (byte) quantity; // 计算CRC校验 int crc calculateCRC(request, 6); request[6] (byte) crc; request[7] (byte) (crc 8); outputStream.write(request); outputStream.flush(); // 读取响应 byte[] response readResponse(5 quantity * 2); return response; }这种实现虽然简单但在实际项目中已经足够使用。需要注意的是不同厂家的PLC可能在协议细节上有些差异需要根据具体设备进行调整。6.2 数据采集应用在数据采集应用中我们通常需要将接收到的数据保存下来。我通常会采用以下策略内存缓存最近接收的数据保存在内存中便于实时显示文件存储定期将数据写入文件防止数据丢失数据库对重要数据进行结构化存储public void saveToFile(byte[] data) { SimpleDateFormat sdf new SimpleDateFormat(yyyyMMdd, Locale.getDefault()); String filename serial_data_ sdf.format(new Date()) .log; try (FileOutputStream fos new FileOutputStream(filename, true)) { fos.write(data); fos.write(\n); } catch (IOException e) { e.printStackTrace(); } }在实际项目中我还会添加文件大小检查当日志文件超过一定大小时自动创建新的文件。这样可以避免单个文件过大导致的性能问题。