如果手上有两块同款 Android 开发板又刷的是同一个固件镜像第一次插上去很可能被adb devices的输出搞得一头雾水两行一模一样的序列号都显示device但你根本不知道哪一行对应哪块板子。想用adb -s指定其中一块不是报more than one device就是运气好连上一块但连到的是哪块全看心情。这个问题在 RK3568、全志 T113、高通 410c 这类 Android 开发板里特别常见尤其是一个人同时调多块板子、或者产线批量烧录后要逐台验证的时候足够浪费一上午。这篇文章不会只给你一个拔掉另一台再连的临时解法而是把 ADB 识别设备的原理、transport_id 的用法、无线 ADB 的切换、以及从根源上把序列号改干净的方法都梳理一遍。适合正在被同类问题折磨的嵌入式开发、安卓系统集成、测试和产线工具链开发的同学直接抄作业。1. 先弄明白ADB 到底拿什么当设备 ID1.1adb devices里的序列号是什么很多人的第一反应是adb devices显示的不就是硬件序列号吗既然是硬件序列号两台板子怎么会一样这里有个认知偏差。ADB 在 USB 连接模式下设备显示出来的 serial 并不一定等于你刻在芯片外壳上的那个硬件序列号。它首先来自 USB 设备描述符里的iSerialNumber字段而 Android 系统在启动时通常把ro.serialno这个系统属性的值填到 USB gadget 的序列号里。ro.serialno则来自 bootloader 传给内核的androidboot.serialno参数或者某些平台上的固定存储区。问题就出在这个链路。很多开发板方案商出厂的工程固件根本没有在 bootloader 里写入独立序列号或者写死了一个公共默认值。于是不管烧多少台板子系统起来后ro.serialno都是同一个字符串USB 描述符里的iSerialNumber自然也就完全相同。你看到的硬件序列号相同实际上是系统上报的序列号相同不一定代表底层芯片 ID 真的重复。所以第一步不要急着怀疑硬件坏了。先分别执行adb shell getprop ro.serialno adb shell getprop ro.boot.serialno adb shell cat /sys/class/android_usb/android0/iSerial如果三个值都一样而且跟adb devices里显示的一致就基本能确认是固件层面写死了序列号。如果ro.serialno和ro.boot.serialno不同那更说明序列号链路里有一个环节没有把唯一 ID 送上来。1.2 两行相同序列号会引发什么乱子正常情况下ADB server 给每个设备分配一个 transport每个 transport 有一个唯一的 serial 字符串。客户端用adb -s serial指定设备时ADB server 会在所有 transport 里精确匹配这个字符串。一旦有两台设备的 serial 完全一样精确匹配就失去意义了。ADB 的匹配逻辑在不同版本里行为不太一样。老一点版本遇到重复 serial直接提示more than one device/emulator新一些的版本可能默认选择其中一个 transport 执行命令但你没法预测选的是哪一块。更麻烦的是像adb install、adb shell、adb reboot这种命令一旦连错了板子轻则装错包重则把正在调试的板子重启掉测试数据全丢。那还能不能区分能。ADB server 虽然对客户端隐藏了一部分细节但adb devices -l会额外暴露一个字段transport_id。这个 ID 是 ADB server 为每个 transport 分配的自增编号只要设备连接没断开它就不会重复也不会因为 serial 相同而混在一起。adb devices -l输出里会出现类似ABCDEF123456 device product:rk30board model:RK3566 device:rockchip transport_id:1 ABCDEF123456 device product:rk30board model:RK3566 device:rockchip transport_id:2注意看serial 两行一模一样但transport_id分别是 1 和 2。这就是我们精准定位每一块板子的钥匙。1.3 为什么说 transport_id 是突破口只要 ADB server 还活着transport_id 就是全局唯一的。你可以用adb -t transport_id来绕过 serial 的重复问题adb -t 1 shell getprop ro.serialno adb -t 2 shell getprop ro.serialno这两条命令会分别落在两块不同的板子上不会再出现选到哪台算哪台的情况。但 transport_id 有个特点它是 adb server 在运行时分配的连接编号。你adb kill-server、重启电脑、拔插 USBtransport_id 都可能重新分配。比如刚才 transport_id 1 是编号靠前的板子 A重启后可能变成 transport_id 2 或 3。所以 transport_id 适合在当次调试会话里使用不适合写死在长期脚本里。理解了这一点后面的所有操作方法就都有了解释。你可以按 transport_id 来手动指定也可以通过无线 ADB 让不同板子以IP:port的形式出现在设备列表里还可以直接把底层序列号改成唯一值让问题从根源消失。2. 三条区分路线物理隔离、IP 直连、改序列号在动手之前先总览一下三个方向根据场景选合适的别一上来就折腾底层分区。方案操作成本稳定性适用场景物理隔离同一时间只连一台最低最高只有一块板子需要反复调试时transport_id 精准指定低高USB 多设备同机连接临时区分无线 ADB用 IP 做设备 ID中高需要长期保持多台在线改掉重复序列号中高最高批量设备管理、产线工具链2.1 物理隔离最朴素但永远有效这是最不容易出错的思路。既然 ADB 认不了相同的 serial那我就不让它同时出现在设备列表里。插上板子 A执行完所有命令拔出再插板子 B。对只有一两块板的个人调试场景这个方案足够。不过要注意单纯物理隔离不代表什么都不用管。插上 A 之后最好先跑一遍adb kill-server再adb devices避免 adb server 缓存了上一次的 USB 状态。实际测试中插拔顺序反了确实会遇到设备状态变成offline的情况。这类方案适合开会前救急但如果你每天要面对二三十块开发板物理插拔能把人累到怀疑人生。2.2 无线 ADB把设备 ID 强制变成 IP:port无线 ADB 的核心思路是绕开 USB serial 这套标识让设备以IP:port的身份出现在 ADB 设备列表里。只要两台板子 IP 不同ADB 就不会再把它们认成同一台设备。操作上需要先在 USB 连接状态下给每台板子打开 TCP 监听端口adb tcpip 5555然后通过网络连接adb connect 192.168.1.101:5555 adb connect 192.168.1.102:5555现在再看adb devices两行显示的是192.168.1.101:5555和192.168.1.102:5555天然可区分。之后所有命令都可以带-s指定 IP不会再跟序列号扯上关系。无线方案有个前置麻烦如果两台板子当前都通过 USB 连着而 serial 又完全相同你没法用adb -s去执行adb tcpip。这时候要么先物理隔离要么用adb -t指定 transport_id。实际操作中我建议直接用adb -t一条命令的事。2.3 改序列号从根上解决物理隔离和无线 ADB 都是绕路真正干净的做法是让每一块板子的序列号唯一。序列号的最终来源通常在 bootloader 层面不同芯片平台的改法不同。Rockchip 一般在 parameter/UBOOT 环境变量里配置全志平台可能在 boot_package 或 sys_config 里高通平台可以走 fastboot 的序列号分区。对普通开发者来说最快的验证办法是先在 userdebug/eng 固件上临时改一改系统属性adb root adb shell resetprop ro.serialno UNIQUE_SN_001 adb kill-server adb devicesresetprop可以覆盖只读属性但前提是固件允许 root而且改完之后要重启 adbd 才能让 USB serial 刷新。这种方法适合验证流程不代表量产时能用。量产固件需要在烧录阶段给每台板子烧入不同的androidboot.serialno这是方案商和产线应该做的底层配置。如果你只是自己维护几块板子又不想动 bootloader那把无线方案和标签台账配合起来也比一直忍受重复 serial 强得多。3. 实操不同场景下的连接步骤3.1 准备工作版本、驱动和授权开始之前先把环境理一遍否则后面排查起来很乱。ADB 工具版本尽量新一些直接使用 Android SDK 自带的platform-tools。打开终端执行adb version版本号在 30 以上一般就能完整显示transport_id字段。太老的 adb 版本可能只在adb devices -l里显示 serial不给你 transport_id那就很难办。Windows 上如果插上开发板驱动装不上优先处理 udev 或者通用 ADB 驱动。Linux 下需要在/etc/udev/rules.d/51-android.rules里把开发板的 USB Vendor ID 加进去并chmod ar然后重启 udevsudo udevadm control --reload-rules sudo udevadm trigger接着打开开发板的开发者选项和 USB 调试。第一次连接时板子上会弹是否允许 USB 调试的对话框勾选一律允许可以省掉后面很多unauthorized问题。3.2 场景 A只有一台需要连接按顺序轮换这个场景最简单但也要规范步骤。把板子 A 插到电脑 USB 口执行adb kill-server adb start-server。执行adb devices确认状态是device。操作完直接adb reboot或adb disconnect然后拔出 USB。插上板子 B重新执行adb devices。如果板子 A 在关掉 USB 调试或重启后没有被正确释放再插 B 时可能出现offline。这时候不要慌先adb kill-server拔掉 B插 A走一遍正常流程再换 B。这套方案虽然笨但对不会配置网络的场景非常有效尤其是在没法给开发板分配固定 IP 的隔离网环境里。3.3 场景 B两台板子同时插在同一个主机上这是这篇文章的核心场景。要同时管理两台及以上重复序列号的板子transport_id是首选。先把两块板子分别插到电脑的两个 USB 口。最好插在不同 USB 控制器对应的口上比如一个插主板后置 USB 3.0一个插前置 USB 2.0减少电气干扰。然后adb devices -l你会看到两行 serial 相同但 transport_id 不同的记录。接下来用-t参数指定adb -t 1 shell getprop ro.serialno adb -t 2 shell getprop ro.serialno怎么确认 transport_id 1 是哪块板子既然序列号一样不能靠 serial 判断可以用物理特征、MAC 地址或者其他配置来反推。比如adb -t 1 shell cat /sys/class/net/wlan0/address adb -t 2 shell cat /sys/class/net/wlan0/address如果两块板子的无线 MAC 不同你就能通过 MAC 把 transport_id 和实际板卡对应起来。没有无线网卡的话也可以读存储分区信息、外设地址或者给一块板子拔掉网络再执行ping之类的命令做区分。如果需要反复操作多个 id写个脚本来枚举最省事for id in $(adb devices -l | sed -n s/.*transport_id:\([0-9]*\).*/\1/p); do echo transport_id: $id adb -t $id shell getprop ro.serialno adb -t $id shell cat /sys/class/net/wlan0/address done这个脚本会把当前在线所有 transport 遍历一遍打印序列号和 MAC。对重复 serial 的板子来说MAC 往往是更好的区分依据。adb logcat抓日志也是一样的逻辑想抓哪台就指定哪个 transportadb -t 1 logcat -c adb -t 1 logcat board1_log.txt adb -t 2 logcat board2_log.txt这个方法能让你在同一台电脑上同时跟踪两块板子的日志效率和舒适度都明显提升。3.4 场景 C无线 ADB 连接多台板子如果开发板已经接入局域网我更推荐切换到无线模式。下面是一个完整流程先通过 USB 把两台板子都连上主机。执行adb devices -l拿到 transport_id。分别执行adb -t 1 tcpip 5555 adb -t 2 tcpip 5555查看板子 IP。可以用adb -t 1 shell ip addr show wlan0也可以用串口、路由器后台确认 IP。网络连接adb connect 192.168.1.101:5555 adb connect 192.168.1.102:5555查看列表adb devices这时候列表里的设备 ID 就是IP:port不再相同。后续指定设备直接写adb -s 192.168.1.101:5555 shell adb -s 192.168.1.102:5555 logcat无线连接在某些开发板上有一个小坑USB 模式下执行adb tcpip 5555后USB 连接会断开显示offline或直接消失这不算异常。还有一种情况是板子重启后不会自动打开 tcpip 监听需要重新执行一次adb tcpip 5555。想让一劳永逸可以把setprop service.adb.tcp.port 5555写进开机脚本或者用adb -t配合自动化脚本在每次重启后统一配置。为了减少 IP 漂移问题尽量在路由器里给每块板子绑定静态 DHCP或者在板子网络配置里写死静态 IP。否则板子重启后 IP 变了ADB 连接列表里还会留着旧的offline记录干扰判断。4. 常见报错与排查清单4.1adb unauthorized反复出现这是开发板调试里最常见的拦路虎。原因通常是电脑端的 adb key 和设备端的授权记录没对上。有两种处理思路一是重新授权。在板子上取消USB 调试授权或者在开发者选项里清除授权记录拔插 USB 后重新点允许。二是清理电脑端的 keyadb kill-server rm -f ~/.android/adbkey ~/.android/adbkey.pub adb start-server执行完再插上板子重新弹窗授权。这个操作适合换电脑、换系统或者批量拷贝过 adb key 的场景。4.2adb devices只显示一台板子两台板子同时插上却只出现一台大概率不是 serial 问题而是 USB 驱动或物理链路问题。先检查线材是否支持数据传输有些充电线只能充电不能传数据。开发板用的是哪个 USB 口很多板子的 OTG 口和普通 USB Host 口混在一起插错了不会触发 adbd。Windows 上是否两个设备用了同一个驱动实例拔掉一个再重新插看设备管理器有没有未知设备。板子供电是否稳定USB Hub 供电不足会导致 adbd 起不来。如果确认物理链路正常可以adb kill-server后重新adb devices。还不行就把 adb server 彻底杀掉拔掉所有设备从第一台开始重新枚举基本能定位。4.3adb offline和more than one deviceoffline状态一般说明 adb server 和设备端版本不匹配或者设备端 adbd 异常。先检查 USB 线、换一个 USB 口再执行adb kill-server adb start-server adb devices如果还是 offline就在板子上重启 adbd有 root 权限adb root之后adb shell setprop ctl.restart adbd没 root 权限直接adb reboot出现more than one device/emulator报错时说明你用了-s但 serial 不唯一或者没带任何指定参数。看到这个报错用adb devices -l查 transport_id然后用-t替代-s问题立刻解决。4.4 无线连接失败cannot connect to 192.168.x.x:5555最常见的是板子和电脑不在同一网段或者路由器开了 AP 隔离。先确认两边 IP 能互相 ping 通。开发板上有ping工具的话可以直接在adb shell里测试。还有就是防火墙拦截了 5555 端口需要放行 TCP 5555。还有个小细节adb connect默认走 5555但有些定制开发板把 adbd 的默认端口改了。这时候adb connect IP:端口里的端口要对应改。5. 从临时连到量产给开发板设备管理的一些建议5.1 序列号唯一化要趁早做如果这批板子以后要长期使用最好别一直靠 transport_id 和 MAC 区分现场越早把序列号刷唯一越好。对量产板核心是在烧录阶段写入唯一标识。具体做法依赖平台但原理都差不多bootloader 读取某个存储区域或烧录参数把值传给内核内核再以androidboot.serialnoxxxx的形式传给 Android init最终反映到ro.serialno。可以把这个值做成烧录脚本里的一个变量批量生产时逐个递增。对已经拿在手里的工程板如果固件允许 root可以试试adb root adb shell resetprop ro.serialno BOARD_A_001 adb kill-server adb devices注意覆盖只读属性不一定在所有机型上都生效而且重启后会恢复。想固化还是要改 bootloader 或系统启动脚本。5.2 建立最简单的设备台账哪怕只有三五块板子也建议建一个表格记录板子上贴的标签名当前ro.serialno实际值无线网卡 MAC 地址固定 IP系统版本和烧录时间备注比如哪块板接了传感器、哪块是备用我自己的习惯是每块板子拿到手先通过 USB 执行一次信息收集for prop in ro.serialno ro.product.board ro.build.version.release; do echo $prop$(adb -t 1 shell getprop $prop | tr -d \r) done然后把输出和板子上的标签贴在同一个表格里。后面无论设备列表里 serial 有多乱只要拿到 transport_id读一次 MAC就能对上号。5.3 用脚本封装日常操作如果每天都要在多块板子之间切换写一个简单的 bash 函数能省很多时间。比如把板名和 transport_id 的对应关系记在文件里然后包一层命令function adb_board() { local board$1 shift local tid$(grep ^$board /tmp/board_map.conf | awk {print $2}) adb -t $tid $ }配合之前的枚举脚本先拿到 transport_id 和 MAC 的映射更新到配置文件里然后就能用adb_board A logcat这种抽象命令操作指定板子。代码量不大但能避免反复复制粘贴一长串-t参数。有一点要提醒只要 adb server 重启transport_id 就会变所以脚本里最好每次从adb devices -l动态获取而不要完全依赖上一次的记录。说实话这个问题不难但很多人卡在第一步默认认为adb devices给出的 serial 就是板上绝对唯一的硬件信息。实际上 ADB 的标识链路里序列号只是其中一环而 USB 描述符、系统属性、bootloader 参数都可能成为相同的源头。搞清楚了这一点再用 transport_id 和无线 IP 去绕开重复基本就能在各种开发板上自由操作了。最后再分享一个小技巧如果哪天你又遇到设备列表混乱先别急着拔线执行一行adb devices -l把 transport_id、MAC、serial 打出来贴到记事本里。这个习惯看起来简单但能帮你省下大量靠猜和靠运气连设备的时间。
