
简介针对Android平台串口通信调试需求这款串口测试工具资源可为移动应用开发、嵌入式设备联调及物联网硬件调试者提供完整的实现思路与可复用的封装逻辑。资源包为rar格式约2.62MB内容聚焦串口通信核心环节涵盖Android权限声明、Android Serial Port Interface等第三方库选型、波特率与数据位等参数配置、串口打开及读写线程处理等技术要点。工具设计上还给出串口选择、参数设定、发送接收文本框等界面布局与事件处理方式并包含异常处理与资源关闭机制其中CommManager类整合了主要串口操作方法便于二次扩展日志记录或数据解析。整体内容既有原理铺垫也接近工程实战适合正在搭建串口调试工具或排查通信问题进行参考。已在CSDN获765人学习下载对需要快速上手Android串口开发的人群具有较好的参考价值。 搞硬件联调的老哥应该都有同感Windows上写个串口工具或者干脆用现成的串口助手双击就完事了。可一旦场景变成“Android平板串口测试工具”事情就开始变得不那么顺了。我前前后后用Android设备做过几款串口测试工具踩过的坑比想象中多得多涉及USB Host权限、内核驱动、设备节点、JNI调用、二进制收发甚至还有芯片厂商私有协议这些破事。这篇就把这套链路完整拆开讲清楚适合后面要做Android串口调试工具、项目验收辅助工具、或者给智能硬件做产测App的开发者参考。所谓串口测试工具核心就三件事找到设备、按正确参数打开、把数据可靠地收下来发出去。听起来和PC上没区别但Android这一层处处受限不把底层逻辑聊透后面排查问题会很痛苦。1. Android串口工具和PC串口工具隔着一整条权限链1.1 Android没有“系统串口API”只有设备节点和USB HostPC上的串口助手本质就是调Windows的Win32 API操作COM口系统层面早就把底层封装好了。Android不一样SDK里根本没有开放类似SerialPort的官方API你只有两条路能碰到串口走USB Host模式通过UsbManager枚举挂在OTG口上的USB转串口芯片直接访问设备内部的板载串口节点比如/dev/ttyS1、/dev/ttyMT0这类节点一般对应主控芯片引出的UART口或蓝牙模块的调试口。这两种方式“看起来”都是打开了串口实际上的权限链路完全不同。走USB Host你要处理的是Android USB框架下的设备授权问题走设备节点你要处理Linux文件权限和SELinux策略问题。很多人在开发前期没分清自己设备到底该走哪条路后面全都卡在权限上。1.2 USB动态授权最先遇到的一道门槛如果你的项目是用USB转串口线CH340、FTDI、CP2102这种连外部设备那app打开串口前必须先拿到USB设备的访问权限。权限弹出这个动作很多人不重视实际上有讲究。在AndroidManifest.xml里声明了usb-device过滤后设备插上会弹一个“允许该应用访问吗”的系统对话框。但注意这个过滤只对已经安装且声明匹配的应用生效开发调试阶段建议还是用代码动态请求UsbManager usbManager (UsbManager) getSystemService(Context.USB_SERVICE); PendingIntent permissionIntent PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE); usbManager.requestPermission(device, permissionIntent);然后注册一个BroadcastReceiver接收ACTION_USB_PERMISSION结果。这里有个坑如果用户点了“取消”并且勾选了“不再询问”后续requestPermission可能不会再弹窗直接返回失败。测试工具App一定要在界面上给出“重新授权”或“到系统设置里清除该App的USB权限”的引导否则用户会一直以为设备坏了。1.3 板载串口节点权限不在App层而在固件层很多工业平板、开发板自带引出串口排针这种场景不经过USB直接操作/dev/ttyS*。但普通App是拿不到这个节点的写权限的——节点默认属于root用户或system组权限可能是crw-rw----App进程的uid是普通应用完全没有访问权。在非root设备上想访问板载串口通常有两条路向设备厂商/系统集成方要一份固件在ueventd.rc里把目标节点权限改成0666并重新打包boot镜像你的App持有系统签名platform签名以系统App身份运行。我见过很多测试工程师拿到一台安卓平板代码写得没问题但一打开串口就报Permission denied然后就以为是自己代码写错了排查半天。遇到这种报错第一反应应该是adb shell ls -l /dev/ttyS*看节点权限而不是反复改Java代码。记住这句话串口打不开八成是权限问题不是代码问题。2. USB转串口芯片识别方案选型和硬件验证顺序2.1 常见芯片与VID/PID速查做Android串口测试工具绕不开USB转串口芯片选型。我在项目里遇到过最多的四类芯片芯片型号厂商典型VID典型PID底层驱动模块CH340 / CH341南京沁恒0x1A860x7523 / 0x5523ch341CP2102 / CP210xSilicon Labs0x10C40xEA60cp210xFT232R / FT2232FTDI0x04030x6001 / 0x6010ftdi_sioPL2303Prolific0x067B0x2303pl2303这个表在调试时用处很大。App里做设备识别的时候大概率就是靠UsbDevice.getVendorId()和getProductId()来判断当前插的是什么芯片再决定用哪套驱动初始化逻辑。2.2 内核驱动缺失是“识别不到”的最大原因如果你的UsbManager.getDeviceList()里都看不到这个USB转串口芯片先别急着改App很可能是设备内核压根没编译对应的USB串口驱动。很多廉价平板、定制主板的kernel是裁剪过的把USB_SERIAL_CH341、USB_SERIAL_FTDI_SIO这种模块直接去掉了。判断方法很简单。插上USB转串口线后执行adb shell dmesg | grep -i usb adb shell ls /sys/bus/usb/devices/如果dmesg里能看到设备枚举成功有new full-speed USB device之类的日志但/sys/bus/usb/devices下找不到对应的tty节点那基本可以确定是内核缺少驱动模块。这种情况App是无能为力的只能换设备或者找厂商编一版带串口驱动的内核。反过来如果getDeviceList()能看到设备但用现成的串口库枚举时报“找不到受支持的设备”那是App里的驱动probe表没有覆盖你的芯片。以常用的usb-serial-for-android为例你可以手动把私有VID/PID挂进去ProbeTable customTable new ProbeTable(); customTable.addProduct(0x1A86, 0x7523, Ch340SerialDriver.class); UsbSerialProber prober new UsbSerialProber(customTable);2.3 用adb快速验证硬件通路别一上来就写App在动手写界面、处理各种生命周期问题之前我强烈建议先在命令行层面把硬件链路打通。这样可以把“硬件坏了”和“App代码有问题”这两个变量彻底分开。以板载串口/dev/ttyS1为例验证数据的完整链路是adb root adb shell chmod 666 /dev/ttyS1 adb shell stty -F /dev/ttyS1 115200 cs8 -cstopb -parenb # 监听串口 adb shell cat /dev/ttyS1 # 另一个窗口发送 adb shell echo test /dev/ttyS1如果这段命令行收发正常说明硬件和驱动都没问题剩下的才是App层面的工作。这个习惯帮我省了无数时间强烈建议也这么做。3. 收发的代码落点打开参数、读线程与Hex转换3.1 两个主流方案怎么选Android串口开发主流的封装库主要有两个方向android-serialport-apiJNI方案直接调用Linux的open、termios系统接口。适合访问板载串口节点/dev/ttyS*需要编译.so但自由度极高能处理任何路径的设备节点。usb-serial-for-android纯Java方案底层走UsbManager的bulkTransfer。支持CH340、FTDI、CP210x等多类USB转串口芯片代码维护更新比较活跃。我的选型逻辑很简单如果是USB转串口设备优先用usb-serial-for-android省事、稳定性好如果是设备内部板载UART节点那就必须上android-serialport-api这类JNI方案。两者应对的场景不完全重叠测试工具做得专业一点可以把两种接入方式都做进去用户可手动选择设备路径或USB设备。3.2 termios配置里最容易出错的一组参数JNI方案下打开串口核心就是termios结构体的配置。网上流传的示例代码很多都有问题我这里给出一段我实际在用、验证过稳定的配置片段struct termios cfg; tcgetattr(fd, cfg); cfmakeraw(cfg); cfsetispeed(cfg, B115200); cfsetospeed(cfg, B115200); cfg.c_cflag | (CLOCAL | CREAD); cfg.c_cflag ~CSIZE; cfg.c_cflag | CS8; // 8数据位 cfg.c_cflag ~PARENB; // 无校验 cfg.c_cflag ~CSTOPB; // 1停止位 cfg.c_cflag ~CRTSCTS; // 关闭硬件流控 cfg.c_cc[VMIN] 1; cfg.c_cc[VTIME] 0; tcsetattr(fd, TCSANOW, cfg);注意几个关键点CLOCAL和CREAD必须保留否则设备可能变成“本地模式”或禁止接收CRTSCTS一定要显式清零尤其当你的USB转串口芯片在某些平台上默认打开了流控时会出现“能发不能收”或者“收一会就卡死”的诡异现象。如果测试对象是RS485半双工设备还要考虑收发切换的GPIO控制但那一层通常由硬件转接板自动完成标准USB转485头一般不用App关心。3.3 读线程设计不要在回调里做UI操作串口数据是持续流入的字节流没有“消息边界”。如果你用InputStream.read()去读它会一直阻塞直到有字节到达。你可能会想那我用while(true)循环读不就行了确实可以但有几个细节决定工具靠不靠谱读线程必须独立于UI线程读到的数据通过Handler或回调抛到主线程刷新界面缓冲区建议用byte[4096]一次读一批减少频繁回调带来的卡顿收到空字节时不要操作UI否则高频率空回调会直接把界面拖死关闭串口时要close()输入输出流并且让读线程优雅退出而不是直接Thread.stop()。一个可靠的最小读线程骨架大概是这个形式while (!isInterrupted) { int len inputStream.read(buffer); if (len 0) { byte[] data new byte[len]; System.arraycopy(buffer, 0, data, 0, len); callback.onDataReceived(data); } }这个循环看似简单但很多人会在异常处理上栽跟头。如果串口被拔出、对端断电read可能抛异常或返回-1这时候要捕获异常并主动通知界面“串口已断开”而不是让线程静默死亡后用户看着界面毫无反应。3.4 Hex转换的两个经典错误串口测试工具离不开Hex显示和Hex发送。这里有两个我见了无数次的低级错误提前帮你排掉第一个发送时把String直接getBytes()。用户在输入框写“FF 01 02”你直接getBytes()发出去发的是ASCII字符“F”“F”“ ”“0”“1”的字节码0x46, 0x46, 0x20...而不是0xFF 0x01 0x02。正确做法是按空格或按两位一组解析成十六进制字节数组。第二个读数据转Hex字符串时用Integer.toHexString(b 0xFF)。这个函数对0x0F这类单字节值会丢掉前导零输出F而不是0F。标准做法String.format(%02X, b 0xFF)如果不想在循环里频繁创建字符串导致卡顿可以考虑用全局的StringBuilder每收到一批数据拼一次然后刷界面。注意这个过程要放在读线程或轻量级处理线程里别直接在主线程做大量字符串拼接。4. 实测中的高发故障乱码、console占用和热拔插4.1 乱码问题不能只怪波特率乱码是串口调试里最经典的问题但很多人一遇到乱码就只想到波特率不对。实际上我总结的排查链路是这样的先确认接线。TTL电平、RS232、RS485三种电平标准不一样USB转串口模块和被测设备这两端必须选对对应的转接方式。TTL模块直连RS232设备轻则乱码重则烧芯片。再确认波特率。115200和9600收发两端不一致屏幕上就是一堆菱形乱码。如果波特率和电平都对但还是偶发乱码检查地线有没有共地。串口通信怕“两地”之间电位差USB转串口模块和设备之间如果没连GND乱码率高得离谱。最后查流控。前面说的CRTSCTS没关或者线材接了RTS/CTS但对端无响应也会导致接收卡顿、数据错位。4.2 板载调试串口被console占用一个非常隐蔽的坑用板载串口做测试工具通信口时最容易踩的坑是Android系统把内核启动日志console log也输出到了这个串口上。你打开串口一收数据全是内核的启动信息、logcat流水日志根本没法用。这种问题的本质是内核cmdline里配置了console/dev/ttyS1之类的参数。注意这个配置通常在bootloader和kernel cmdline层面App无法修改。遇到这种情况要么找厂商换一个没被console占用的UART口要么在系统层面把cmdline里的console...参数去掉。很多开发板出厂默认所有UART都被console占着调试工具想用也得先把这个抢过来。4.3 热拔插之后串口失效是因为你没有监听拔插事件测试工具在现场使用经常需要频繁更换被测设备USB转串口模块拔了又插。如果你只在打开页面时枚举一次设备拔插之后串口几乎必然失效。更严重的情况是拔掉时读线程还阻塞在read()里再插回来打开新句柄两个线程同时操作直接导致native层崩溃。标准做法是注册ACTION_USB_DEVICE_ATTACHED和ACTION_USB_DEVICE_DETACHED两个广播。收到DETACHED时立刻关闭当前所有串口句柄并读线程退出收到ATTACHED时重新枚举设备并自动重连或提示用户一键重连。另外千万不要用静态变量长期持有UsbSerialPort实例拔插之后那个实例已经和底层文件描述符断开了复用只会出奇怪的问题。4.4 串口打开频率过高导致文件描述符泄漏还有一个我在长时间压测中遇到的内存问题每次打开串口都new SerialPort(...)用完不关闭线程和流。别小看这个Android系统对单个进程的文件描述符数量限制通常是1024如果你在测试工具里做自动化用例每小时开关串口几百次一段时间后应用会突然各种打开失败、甚至native crash。排查方法是用adb shell cat /proc/pid/fd | wc -l看fd数量。工具里做一个强制“关闭所有串口”的兜底按钮在产品化时很有必要。5. 测试工具的自检从回环短接到长时间压测5.1 回环测试证明你的工具收发链路是通的写好的测试工具上线前一定要先自检。最简单可靠的方式是做回环测试把USB转TTL模块的TX和RX引脚用一根杜邦线短接让数据发出去之后原路返回。操作步骤很简单打开你做的测试工具选好设备勾选“Hex发送”然后循环发送一串递增帧比如AA 01 01 AA、AA 02 02 AA。如果接收区能看到完整返回说明驱动、打开参数、读写线程、UI显示整条链路都正常。这一步能过滤掉大量“我以为我写了正确代码”的问题。比如波特率设错了、Hex转换写错、读线程没起来回环测试统统能暴露。5.2 高压下验证可靠性的两个关键指标回环只能证明“能通”但测试工具是给人长期用的必须做一轮高压测试。我常用的两个压测方法第一个是丢包压测。Android端循环发送带序号的数据包序号从0递增到65535重复上位机PC端通过另一个串口助手接收并校验序号。如果上位机收到的序号不连续说明Android端发送有丢包或乱序。第二个是接收吞吐测试。从PC端串口助手以最快速度连续发送大块数据比如一次发16KB观察Android端界面能否实时跟进、App内存和CPU占用是否失控。这里要注意如果被测设备长时间不发送数据读线程空转阻塞理论上不会占CPU但有些库实现有bug会忙等busy-loop必须实际监控CPU确认。5.3 向协议自动化验证方向延伸测试工具做到收发稳定之后再往上走就是针对具体协议的验证功能。比如在安防行业做GB28181设备或NVR产品时虽然主流程走网络协议但很多外设配置、底层日志还是要靠串口。工具如果能支持自定义协议模板比如Modbus RTU、自定义帧头长度CRC在收到字节流时自动按协议拆包、校验、标记错误帧就能从“串口助手”升级成“自动化验证工具”。这块不用一步到位可以先做两层第一层原始字节流透传显示第二层按一个简单规则做断帧和校验比如指定的帧头帧尾、或按长度字段拆分。有了这两层后面再做自动化脚本就有基础了。最后再分享一个经验测试工具这类App别做太重但“串口历史记录导出”功能一定要有。现场联调时经常需要把抓到的串口日志发给固件开发同事分析没有导出功能光靠截屏根本说不清楚。把接收区内容按时间戳存成文本文件一个FileProvider直接分享出去这一步能极大提升工具的实际使用价值。本文还有配套的精品资源点击获取