去年有段时间我一直在做手机产线的老化测试上位机。测的产品是 Android 系统的移动终端,但整个测试框架必须用 LabVIEW 搭,两边要联动:装 App、清缓存、模拟点击、抓日志、读系统版本、控制相机拍照,这些操作全都夹在整套 LabVIEW 测试流程中间。一开始想的是找人手工操作,结果测试节拍完全跟不上,后来干脆把 adb shell 命令在 LabVIEW 里直接跑起来,一条命令解决一件事,整个自动化流程才真正走通。
如果你也是做终端设备测试、产线自动化,或者正在 LabVIEW 上位机里集成 Android 设备控制,这篇文章应该能帮你少走很多弯路。我会把从环境准备、子 VI 封装到异常排查的完整过程都写清楚,包括那些文档里不会写的坑。
1. 先想清楚:为什么 LabVIEW 要调 adb shell
1.1 adb 在终端自动化测试中的定位
adb 全称 Android Debug Bridge,平时我们叫它安卓调试桥。严格来说它不是一个测试工具,而是 Google 官方提供的通用调试通道。它由三部分组成:跑在电脑端的 adb 客户端、电脑后台的 adb server 服务进程,以及跑在 Android 设备上的 adbd 守护进程。LabVIEW 要做的只是调用电脑端的 adb 客户端,剩下复杂的 USB 通信、协议交互、设备发现全都由官方实现接管。
这套机制的好处非常明显:LabVIEW 开发者不需要懂 Android 的底层驱动,不需要自己实现 USB 通信协议,只需要把命令字符串拼好、扔出去,就能完成对设备的控制。比如你想知道当前设备型号,执行adb shell getprop ro.product.model就能拿到结果;想模拟一次屏幕点击,执行adb shell input tap 540 960即可。这种“字符串即命令”的模式,天然适合 LabVIEW 这种图形化编程环境里快速集成。
1.2 调用 adb 相比其它方案的取舍
做 LabVIEW 与 Android 交互其实有三条常见路:一条是走网络 Socket,让设备端装一个自定义服务程序;一条是用共享库方式直接封装 Android 调试协议;还有一条就是我现在要讲的 adb shell 命令方式。三条路我也都试过,各自的优缺点非常明显。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| adb shell 命令 | 开发量小、无设备端依赖、跨 Android 版本稳定 | 单条命令开销略大、大约几十毫秒到几百毫秒 | 产线测试、功能控制、设备状态读取 |
| 网络 Socket | 延迟低、数据吞吐大 | 需要在设备端单独开发服务程序 | 高频流式数据采集、大文件传输 |
| 直接封装调试协议 | 控制粒度最细 | 工作量大、版本兼容性自己维护 | 有特殊通信需求的底层开发 |
我的经验是,除非你确实需要高频率、低延迟的数据流,比如以 100Hz 以上采集传感器数据,否则在产线和功能测试场景里,adb shell 命令是性价比最高的选择。它不需要在每一台被测设备上预装任何东西,只需要设备开启 USB 调试,插上数据线就能工作。这在批量测试时尤其重要——省掉了对每台设备安装 agent 的环节,整个测试准备时间能缩短一大截。
2. 在 LabVIEW 里执行 adb 命令,选对调用方式很重要
2.1 System Exec.vi 为什么是首选
LabVIEW 里执行外部程序有好几种方式。最常用的就是 Connectivity 函数面板下的 System Exec.vi(系统执行.vi),它本质上就是封装了 Windows 的进程创建机制,你在命令行里能敲什么,它就能执行什么。我对比过其它几种方式:
- 调用系统 DLL 或者 .NET 的 Process 类,功能当然更灵活,但你要自己处理进程句柄、输入输出流重定向,开发量上去了,稳定性反而更难保证。
- 用 LabVIEW 自带的共享库调用节点去封装 libadb 之类的库,你得先搞懂 adb 的源码和接口,门槛高,日常维护麻烦。
- System Exec.vi 则把这些都封装好了。你在前面给命令行字符串,设置超时时间,它执行完把标准输出、错误输出和退出代码都还给你。典型的一条命令几十毫秒,对绝大部分测试场景完全够用。
我的实际建议是:优先用 System Exec.vi 把整个调用逻辑跑通,等性能不够了再去考虑其它方案。不要一开始就过度设计。
2.2 参数与工作目录的处理要点
System Exec.vi 的面板上有一排关键输入:command line(命令行)、working directory(工作目录)、standard input(标准输入)、timeout(超时),以及三个输出:standard output、error output、exit code。我踩过最大的坑就是工作目录。第一次用的时候没填 working directory,结果在打包成 exe 后执行adb命令总是报“系统找不到指定的文件”,后来发现是运行环境里没有继承开发机的 PATH 环境变量。
解决方式有两种,我的习惯是双保险:
- 把 Android platform-tools 的完整路径拼到命令行前面,比如
C:\platform-tools\adb.exe devices。 - 或者在 System Exec.vi 的 working directory 里显式指定
C:\platform-tools。
这两者取其一即可,我更推荐第一种,因为后续如果要多台设备、多个平台工具版本切换,把路径作为参数传进来更灵活。另外注意一个细节:如果路径里有空格,一定要用双引号包住,写成"C:\Program Files\platform-tools\adb.exe"devices,否则会直接被拆成两个参数,命令执行必失败。
2.3 每条命令都是独立进程,别让状态留在上一条
System Exec.vi 每调用一次,都会创建一个全新的进程。这对 adb 来说尤其需要注意。举个例子,Linux 下你用cd /sdcard && ls这样的命令把目录切换和列目录合并在一起执行没问题,但如果你先在一条命令里cd /sdcard,再在下一条命令里执行ls,得到的仍然会是默认目录的内容。因为第二条命令启动的是一个全新的 adb shell 会话,前一条命令里的状态已经全部丢失。
所以在封装时一定要把“一整件操作”拼成一条命令行去执行。比如想清理某个 App 的缓存目录,应该直接写adb shell "cd /sdcard/xxx && rm -rf cache",而不是分两步。这个思维转变很重要,一开始我总下意识地按照 LabVIEW 里顺序执行的习惯,把命令拆成多行,结果状态全丢了,排查了好一阵子才反应过来。
3. 实操记录:5分钟封装一个通用 adb shell 子 VI
3.1 环境准备与命令验证
在动手写 LabVIEW 代码之前,先把电脑端环境准备好。你需要从 Android 官网下载 Platform Tools 压缩包,解压到某个固定目录,比如D:\platform-tools。里面最重要的就是 adb.exe、AdbWinApi.dll、AdbWinUsbApi.dll 这三个文件,缺一不可。
设备上要做的准备工作是开启开发者选项和 USB 调试。不同品牌的手机入口不太一样,但一般都是在“设置-关于手机”里连续点击版本号 7 次,然后进入“开发者选项”打开 USB 调试。插上数据线后,电脑端执行adb devices,第一次连接设备上会弹一个“允许 USB 调试吗”的授权框,需要手动勾选“一律允许”并确认。
我个人强烈建议,在把所有命令集成到 LabVIEW 之前,先在命令行窗口里把每一条要用到的命令手动跑一遍,确认能拿到预期输出。这一步花不了几分钟,但能省掉后面在图形化代码里排查字符串拼写错误的大量时间。
3.2 子 VI 的输入输出设计
通用执行器的核心是一个子 VI,我给它起名叫ADB_Shell.vi。这个子 VI 的输入输出设计如下:
| 方向 | 连接线名称 | 类型 | 说明 |
|---|---|---|---|
| 输入 | adb path | 字符串 | adb.exe 的完整路径,默认填 “adb” |
| 输入 | device serial | 字符串 | 设备序列号,多设备时必填 |
| 输入 | shell command | 字符串 | 要执行的 adb shell 命令 |
| 输入 | timeout ms | 数值 | 超时时间,默认 5000 ms |
| 输出 | standard output | 字符串 | 命令的标准输出 |
| 输出 | error output | 字符串 | 命令的错误输出 |
| 输出 | exit code | 数值 | 进程退出码,0 表示成功 |
在 VI 内部,命令行字符串通过“格式化写入字符串”来拼接。如果是带 shell 前缀的命令,拼成执行"__adb_path__" -s __serial__ shell __command__;如果是不带 shell 的纯 adb 命令,就拼成执行"__adb_path__" -s __serial__ __command__。-s参数指定设备序列号,这个参数平时容易漏,但在多设备连接同一台电脑时,没有它你会被“more than one device”的报错折腾到怀疑人生。
3.3 构建执行器面板的关键步骤
打开 System Exec.vi 后,连线其实很简单,但有几个容易被忽略的配置项:
第一个是 Standard Input 接线端。如果你只是执行普通命令,这个输入可以留空,但要注意把它连接一个空字符串常量,避免一些版本下出现输入阻塞。
第二个是 Run Minimized 或者叫 run minimized 的布尔输入。这个建议设置为 True,让执行 adb 命令时不要在后台闪出黑色命令行窗口。在开发环境里闪一下还能忍,但打包成 exe 给产线用的时候,满屏的命令行窗口会让操作员以为程序出了什么严重故障。
第三个是 timeout 的处理。System Exec.vi 的 timeout 接线端如果给 0,在某些版本里表示无限等待。这就很危险,因为 adb 命令有时候会因为设备异常而一直卡住不返回。我一般默认给 5000 毫秒,如果是安装包这类耗时操作,单独给到 30000 毫秒。这个参数必须根据命令类型动态调整,统一用一个大超时会影响测试节拍。
3.4 几组高频 adb 命令模板
我把自己在项目中反复用到的几组命令整理出来,这些都是可以直接套用的模板。
检查设备是否在线并获取基本信息,执行:
adb -s 设备序列号 shell getprop ro.product.model adb -s 设备序列号 shell getprop ro.build.version.release安装和卸载应用:
adb -s 设备序列号 install -r "D:\apk\app-debug.apk" adb -s 设备序列号 uninstall com.example.app模拟点击、滑动和文本输入:
adb -s 设备序列号 shell input tap 540 960 adb -s 设备序列号 shell input swipe 540 1500 540 500 300 adb -s 设备序列号 shell input text "hello"截图并回传到电脑:
adb -s 设备序列号 exec-out screencap -p > "D:\pic\screen.png"这条截图命令有几个要点。第一要加exec-out而不是shell,因为 exec-out 输出的是原始二进制流,直接重定向到文件就是完整 PNG;如果用shell screencap,有可能会因为行尾转换导致图片文件损坏。第二是重定向符号>,在 Windows 命令行下有效,但在 System Exec.vi 里能否直接使用,取决于它是不是交给 cmd.exe 处理的,我在后面的避坑章节会细讲。
获取当前前台应用包名:
adb -s 设备序列号 shell dumpsys window | findstr mCurrentFocus在 LabVIEW 里,这些命令都只是字符串常量,拼好后喂给 ADB_Shell.vi 就行。输出结果要用“匹配模式”或“扫描字符串”去解析,提取你想要的字段。比如解析设备型号,只要在输出字符串里按换行符断开,取第一段非空文本即可。
4. adb 命令的坑:超时、乱码、多设备
4.1 命令卡死与超时机制
adb 命令执行不像本地命令那么“脆”,它受 USB 连接状态、设备负载、驱动稳定性多方面影响。最常见的现象是某个操作执行到一半,设备端 adbd 进程暂时无响应,整个命令卡在那里。在命令行窗口里你还能按 Ctrl+C 终止,在 LabVIEW 的 System Exec.vi 里,如果超时设置不当,整个 VI 会一直占用,连测试程序的界面都跟着卡住。
我的处理策略是分级超时。普通 getprop、input 这类轻量命令给 3 到 5 秒;install 命令给 30 秒以上;涉及数据传输的重操作,比如 pull 大文件,给到 60 秒,甚至更长。并且我习惯在超时后检查 exit code 和 error output,如果确实因为超时被杀掉,就记录日志并执行一次设备重连流程。
还有一个小技巧:System Exec.vi 超时后,它返回的 exit code 往往不是 0,此时最好不要立刻重试同一命令,而是先执行一次adb devices确认设备状态。设备可能已经处于异常状态,继续发指令只会让问题加剧。
4.2 中文与特殊字符编码
输出乱码是我被问得最多的问题之一。原因很简单:adb 的输出通常是 UTF-8 编码,而 Windows 上的中文环境默认用 GBK 或者 GB2312。System Exec.vi 返回的字符串如果直接在 LabVIEW 前面板显示,中文就变成了一堆乱码。
处理办法是在显示前做一次编码转换。LabVIEW 里可以用“代码转换”函数,把 UTF-8 的字节数组转换成当前系统编码的字符串。具体做法是先用“字符串至字节数组转换”拿到字节流,再指定源编码为 UTF-8,目标编码为空(表示用系统默认),转换完成后就能正常显示了。如果你的设备输出包含 UTF-8 中文,这一步基本是绕不开的。
特殊字符方面,我最头疼的是命令里的空格、引号、双引号以及&、|、>这类特殊符号。在命令行中,这些字符都有特殊含义。比如想查询 system_server 进程信息并过滤关键字,直接写:
adb shell ps | findstr system在命令行窗口里没问题,但如果放到 System Exec.vi 里,它未必会把整条命令交给 cmd.exe 处理,这时|可能被当成普通字符传给 adb,结果和你预期完全不一样。解决办法是使用 cmd.exe 的/C参数显式包装整条命令:
cmd /C adb shell ps | findstr system这样整条命令由 cmd.exe 解析,重定向和管道符都能正常工作。这个细节让我印象很深,因为第一次集成时,我一直以为是 adb 命令写错了,完全没往解析器层面想。
4.3 多设备选择与稳定重连
一台电脑带多台 Android 设备做并行测试,是很典型的场景。adb 官方给的答案是-s 序列号参数。只要你在每条命令里都带上-s,adb 就会精确指向对应设备。需要注意的是,不同品牌的设备序列号格式五花八门,有的是纯数字,有的是字母数字混合,建议在程序启动时扫描一次adb devices,把所有设备序列号枚举出来,做成下拉列表让操作员选。
另外,批量测试时 USB 接口供电不足、线材老化、插拔频繁,容易导致设备掉线。我处理掉线的标准流程是:
- 执行
adb devices确认设备状态。 - 如果是 offline 或 unauthorized,先执行
adb kill-server。 - 再执行
adb start-server重启服务。 - 最后再执行
adb devices确认设备恢复。
这套流程看起来粗暴,但实测下来成功率很高。毕竟 adb server 杀掉了重新拉起来,会强制重新枚举一次 USB 设备,很多因为服务僵死导致的离线问题就好了。注意在做这套恢复动作时,子 VI 的超时要给足,因为 kill-server 和 start-server 本身就耗时,原来 5 秒的超时容易提前触发,导致误判。
5. LabVIEW + adb 常见问题排查实录
我做了一张速查表,把最常见的几类问题整理出来。这张表是我在实际调试中一笔一笔记录的,比很多官方文档里的故障描述要直接得多。
| 故障现象 | 根本原因 | 解决办法 |
|---|---|---|
| 提示 “adb 不是内部或外部命令” | 未配置 PATH 或未指定 adb.exe 完整路径 | 在命令行里写入完整路径,或设置 working directory |
| adb devices 看不到设备 | USB 调试没开、驱动异常或线材问题 | 检查设备端授权弹窗,换原装数据线,重装 USB 驱动 |
| 设备状态显示 unauthorized | 首次连接未确认授权 | 在设备上勾选“一律允许”,重新插拔 |
| 命令执行后没有输出 | 命令被管道符或重定向符拆解,未正常交给 cmd 解析 | 使用 cmd /C 包装整条命令 |
| 中文输出乱码 | UTF-8 与系统编码不一致 | 使用代码转换函数把 UTF-8 转为系统默认编码 |
| 执行 install 超时 | APK 包太大或 USB 传输慢 | 单独加大该命令的超时时间,建议 30 秒以上 |
| 提示 more than one device | 连接了多台设备但没指定序列号 | 所有命令加 -s 设备序列号 |
| 端口 5037 被占用 | 有旧 adb 进程残留或被其它工具抢占 | 任务管理器结束 adb.exe,执行 adb kill-server |
| 后台一闪而过的黑窗口 | System Exec.vi 未启用最小化运行 | 把 run minimized 设为 True |
| 并发执行多条命令时相互干扰 | 多个命令同时调用同一 adb server | 为每条命令加锁,或用队列串行化调用 |
除了表格里这些,还有两个我特别想强调的隐蔽坑。第一个是路径中含空格的问题。如果 adb.exe 放在C:\Program Files\platform-tools下,命令行必须写成"C:\Program Files\platform-tools\adb.exe" devices,少一对双引号就会报“系统找不到指定的文件”。这个错误在开发机上因为路径通常比较干净不容易暴露,一部署到客户电脑就翻车。
第二个是并发调用 adb server 的竞争问题。adb server 是共享的,多个进程同时发起 adb 命令时偶尔会触发内部竞态,导致某条命令返回“error: closed”。我在产线多工位同时测试时遇到过好几次。解决方式是在 LabVIEW 里用一个全局队列把所有 adb 命令串行化,或者给每个命令前面加一个互斥锁。虽然牺牲了一点并行度,但稳定性大幅提升。
6. 进阶扩展:从“执行命令”到“自动闭环”
6.1 队列化与状态机封装
当 adb 命令数量多起来之后,直接在框图上一个个放 ADB_Shell.vi 节点,逻辑会迅速变得混乱。我后来把整个命令调用改成了基于队列的状态机架构。界面输入一个操作类型,比如“安装”“点击”“截图”“读取状态”,程序把这些操作翻译成对应的命令行字符串,压入队列,再由一个专门的处理循环逐个取出执行。
队列化的好处不只是代码整洁。它能天然避免多个命令并发调用 adb server 的竞争问题,还能精确控制执行顺序。比如先安装、再启动、再截图,这三个操作之间有明确的先后依赖,串行队列让这种流程控制变得非常简单。另外,队列里可以加入重试逻辑:某条命令失败后自动回退到上一步,或者重新连接设备后再执行一次,这在长时产线运行中极具价值。
6.2 截图回传与视觉闭环
截图命令值得单独说一说,因为它能直接打通“视觉识别 + 自动操作”的闭环。我之前做过一个相机自动测试项目,逻辑是先通过 adb 发起拍照命令,然后截图回传,再用 LabVIEW 的 IMAQ 视觉函数分析图片亮度、清晰度是否达标,最后根据分析结果决定下一步操作。
整套流程的底层就是一行截图命令,但要做好还需要处理文件时序。截图回传是异步过程,截图完成、文件写入、回传完成各有一个时间点。最保险的做法是截图命令执行成功后,等待 300 到 500 毫秒再检查目标文件是否存在,文件大小是否大于 0,然后再进行后续分析。否则经常拿到半截图片,视觉分析结果完全不可信。
6.3 实时获取 logcat 日志流
日志抓取在稳定性测试里是刚需。常见的做法是执行adb logcat并重定向到文件,但 System Exec.vi 是“执行完一次性返回结果”的模式,而 logcat 默认是持续输出不结束的,这会导致命令一直卡到超时,中间的数据你也拿不到。
正确的思路是启动一个单独的异步进程来跑 logcat,把日志写到电脑本地文件,然后 LabVIEW 主程序用“读文件”的方式定时从这个文件尾部读取增量内容,供界面显示或者做异常关键字监测。这相当于把 logcat 变成了一个后台数据源。我第一次这么实现的时候,特意把文件读取函数设置成共享文件允许读写,否则 LabVIEW 会报文件被占用。
6.4 最后再分享一个个人小技巧
调试 LabVIEW 与 adb 联调时,我始终在电脑上开着两个窗口:一个是系统命令行窗口,用来快速验证 adb 命令本身是否正确;另一个是设备端的 logcat 窗口,用来观察设备端执行结果。遇到问题先把 LabVIEW 里的命令拷贝到命令行窗口手动跑一遍,如果命令行窗口也执行不了,那就是命令本身或者环境问题,跟 LabVIEW 无关;如果命令行窗口能执行,再回头查独立 VI 的参数设置。
这个方法看起来平平无奇,但真的能省掉大量在图形化代码里来回连线的排查时间。命令的拼写、引号、路径,这些直接影响成败的细节,在命令行窗口里暴露得又快又清楚。联调阶段的痛苦,基本都集中在“命令是对的,但 LabVIEW 执行结果不对”和“LabVIEW 是对的,但是命令本身就错了”这两个方向上,这个窗口对比法能把两者快速区分开来。