如果手上有两块同款 Android 开发板,又刷的是同一个固件镜像,第一次插上去很可能被adb devices的输出搞得一头雾水:两行一模一样的序列号,都显示device,但你根本不知道哪一行对应哪块板子。想用adb -s指定其中一块,不是报more than one device,就是运气好连上一块,但连到的是哪块全看心情。这个问题在 RK3568、全志 T113、高通 410c 这类 Android 开发板里特别常见,尤其是一个人同时调多块板子、或者产线批量烧录后要逐台验证的时候,足够浪费一上午。
这篇文章不会只给你一个"拔掉另一台再连"的临时解法,而是把 ADB 识别设备的原理、transport_id 的用法、无线 ADB 的切换、以及从根源上把序列号改干净的方法都梳理一遍。适合正在被同类问题折磨的嵌入式开发、安卓系统集成、测试和产线工具链开发的同学直接抄作业。
1. 先弄明白:ADB 到底拿什么当设备 ID
1.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、重启电脑、拔插 USB,transport_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 a+r,然后重启 udev:
sudo 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抓日志也是一样的逻辑,想抓哪台就指定哪个 transport:
adb -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 后重新点"允许"。
二是清理电脑端的 key:
adb 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 device
offline状态一般说明 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.serialno=xxxx的形式传给 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 打出来贴到记事本里。这个习惯看起来简单,但能帮你省下大量靠猜和靠运气连设备的时间。