LabVIEW调用adb shell操作Android设备:自动化测试完整指南

发布时间:2026/10/5 0:56:13
LabVIEW调用adb shell操作Android设备:自动化测试完整指南
干产线测试或者设备联调的朋友应该都有这种体验工位上的上位机是LabVIEW写的被测对象却是一台Android平板、手机或者扫码终端。程序要自动点击某个按钮、输入一串字符、截一张图回传还要判断界面有没有异常最省事的办法不是去Android端专门写一套Socket通信服务而是直接让LabVIEW去调adb shell命令。这篇文章就把“LabVIEW执行adb shell操作”这件事从头到尾聊透包括适用场景、底层原理、几种实现方案、输出解析方法、常见坑以及我实际项目里跑过的完整案例。写这篇文章的初衷是我见过太多人卡在同一个地方LabVIEW本身做上位机控制界面、数据采集都很在行但一碰到“要和Android设备交互”就不知道该从哪里下手。有的人绕了一大圈去开发Android端的TCP Server甚至用串口转接板做物理层通信成本和维护难度都上来了。实际上Android系统本身就预留了adb调试通道只要在PC侧把命令发出去设备就能执行点击、滑动、截图、装包、拉日志这些动作。这篇文章适合正在用LabVIEW做上位机、想快速把Android设备纳入自动化测试体系的工程师阅读也适合刚接触LabVIEW但有一定编程基础、想扩展技能树的人参考。1. 测试台架上为什么绕不开adb shell我做过的几个项目里LabVIEW和Android设备要打交道的场景几乎都是同一个套路被测产品是一台Android系统的设备但测试工装、数据采集、逻辑控制全部由LabVIEW完成。早期的测试方法很原始测试员拿着设备在工位上手动点击屏幕看到现象后在电脑上点“合格”或者“不合格”一天下来重复几百次效率低不说还容易漏测。后来把这种人工操作改成自动化第一个问题就是LabVIEW怎么去“操作”一台Android设备当时想过几个方案比如在Android端写一个后台服务通过TCP或者USB与LabVIEW通信再由后台服务模拟点击。理论上可行但问题也很明显每台被测设备都要预装App还得处理App崩溃、权限弹窗、版本兼容这些问题而且一旦被测对象就是设备系统本身比如系统级Launcher测试被测程序挂了控制通道也没了这是典型的“自己掐自己脖子”。adb shell不存在这个问题。adbAndroid Debug Bridge是Android开发工具链里自带的多用途调试工具通过USB或者Wi-Fi连接设备后在PC的命令行里敲adb shell input tap 500 1000设备屏幕就会在对应坐标处产生一次触摸事件敲adb shell screencap -p /sdcard/s.png设备就会把当前屏幕截图保存下来。它不依赖设备上任何第三方App也不需要在被测系统上提前部署什么只要开启USB调试PC就能拿到控制权。这就是为什么测试台架上绕不开adb shell它是Android设备在PC侧最通用、最底层、最不依赖业务App的操作通道。LabVIEW负责“大脑”做流程控制、数据采集、结果判断adb shell负责“手脚”做设备操作和状态读取。两个角色非常清晰。你的LabVIEW上位机再花哨最终还是得通过一个具体通道把“命令”送到设备上adb shell就是那个通道。理解了这一点后面所有的技术细节就都有了解释前提。2. adb shell能为自动化测试提供哪些“手脚”既然决定了走adb shell路线那首先要清楚这个“手脚”能做什么不能做什么。很多第一次接触的人只知道adb install装个APK其实adb shell能做的事情远比想象中多。我自己在LabVIEW测试程序里用得最多的基本可以分成下面几类。第一类是模拟操作输入。adb shell input后面可以接tap、swipe、keyevent、text等子命令分别对应点击、滑动、物理按键、文本输入。比如input keyevent KEYCODE_HOME等价于按Home键input keyevent KEYCODE_BACK等价于按返回键。对于LabVIEW来说这就是最直接的“机械手”。第二类是屏幕截图和录屏。screencap可以直接把当前屏幕保存为PNG图片放到设备SD卡上配合adb pull把文件拉到电脑上。这个操作的价值在于测试程序每执行一个动作都可以通过截图验证界面状态。截图拿回来以后用LabVIEW的视觉模块NI Vision做模板匹配或者像素校验就能判断当前界面是否符合预期这等于给自动化测试装了“眼睛”。第三类是系统状态查询。比如adb shell dumpsys battery可以拿到当前电量、温度、电压adb shell dumpsys window可以查到当前顶层窗口的Activity类名adb shell getprop可以拿到设备型号、系统版本、屏幕分辨率。这些信息在产测中非常有用可以自动判断被测设备是否就位、配置是否正确。第四类是应用和进程管理。adb install和adb uninstall对应装包和卸包adb shell am start -n 包名/Activity名用来启动指定界面adb shell am force-stop 包名用来强制杀掉进程。做冷启动测试、异常恢复测试时这一组命令是核心。我整理了一个高频命令表做测试程序时几乎每天都会用到需求命令示例说明查看设备在线状态adb devices输出serial和state模拟点击adb shell input tap 520 900x/y为屏幕坐标模拟滑动adb shell input swipe 100 100 500 500 300最后一个参数是滑动时长ms模拟返回键adb shell input keyevent KEYCODE_BACK也可以用数字4输入英文字符adb shell input text hello直接输出不支持中文截屏保存到设备adb shell screencap -p /sdcard/s.png-p表示PNG格式拉取设备文件到PCadb pull /sdcard/s.png D:\img\反向是adb push安装APKadb install -r test.apk-r表示覆盖安装启动指定Activityadb shell am start -n com.test/.MainActivity包名/类名强制停止应用adb shell am force-stop com.test冷启动测试常用查询电池状态adb shell dumpsys battery可解析电量和温度查询设备型号adb shell getprop ro.product.model返回型号字符串有了这些“手脚”在LabVIEW里做Android设备的黑盒自动化测试就有了可靠的操作集。剩下的问题只有一个LabVIEW怎么把这些命令执行起来并且拿到结果。3. LabVIEW调用adb的两条技术路线命令行与APILabVIEW调用外部程序底层有两条完全不同的路线。一条是使用现成的“System Exec.vi”系统执行节点把整条命令当一个字符串丢给操作系统去跑这是最直接、最少代码的方式。另一条是通过.NET接口调用System.Diagnostics.Process类自己创建进程、重定向输入输出、控制超时这种方式步骤多一些但可控性完全不一样。很多人一上来就选第一条遇到超时、弹窗、编码问题就卡住不知道怎么办。我自己的经验是两条路都要会根据场景换着用。先看System Exec.vi。这个VI在函数选板“互联互通”下的“库与可执行程序”分类里中文LabVIEW里也能通过搜索“System Exec”或者“系统执行”找到。它做的事情本质上是把命令字符串交给操作系统的命令行解释器去执行然后等待程序退出把标准输出和标准错误以字符串形式返回。优点是接线简单适合执行一次性的命令缺点也很明显没有直接可用的超时参数如果命令不退出VI会一直阻塞在那里看起来就像整个程序死掉了。再看.NET Process路线。LabVIEW对.NET的支持已经很成熟调用System.Diagnostics.Process类可以创建一个进程对象设置FileName为adb.exe的完整路径Arguments为命令参数然后把RedirectStandardOutput设为True就能把命令输出重定向到一个流对象里再异步读取文本。这种方式最大的好处是可以调用WaitForExit(超时毫秒数)来设置超时命令挂死也能自动超时返回另外可以设置CreateNoWindow为True彻底不闪黑窗口。还有第三条路线是借助OpenG开源工具包。OpenG Toolkit里有一套封装好的命令行执行VI本质上是对System Exec或者.NET Process的再封装增加了超时、错误处理、字符串编码这些细节。如果你想快速用起来安装OpenG之后直接用它的“Execute Command”类VI也算省事。但引入第三方依赖需要在VIPM里装包而且OpenG的VI风格和原生LabVIEW略有差异我建议先把前两种路线掌握再考虑要不要用工具包。我画过一张对比表做方案选型时很有用指标System Exec.vi.NET ProcessOpenG实现难度低中低超时控制无有有隐藏窗口直接不弹窗CreateNoWindowTrue基本支持输出编码控制弱强中依赖第三方包否否是适合场景简单命令、短耗时操作长时间老化测试、严苛场景快速集成的中小型项目从最终效果上说System Exec.vi已经能满足80%的日常需求剩下的20%如果不想被坑死就得用.NET Process来补。下面两章会分别讲这两条路线的具体实现细节。4. 方案落地System Exec.vi的完整接线与参数取舍先讲System Exec.vi这条最简单的路。它的输入输出引脚包括command line命令行字符串、working directory工作目录、standard input标准输入、wait until completion?是否等待完成、run minimized?是否最小化运行和use standard error?是否使用标准错误输出有standard output、standard error和exit code。接线时最容易犯的错误是把“命令字符串”和“命令加参数”混为一谈。比如要在LabVIEW里执行adb设备列表命令我见过有人直接在command line里写adb devices看起来没错但如果adb.exe没有加入系统PATH环境变量实际执行时会报“找不到adb命令”。更稳妥的做法是写完整路径并且把路径用双引号包起来C:\Android\platform-tools\adb.exe devices为什么路径要用双引号包起来因为Windows命令行把空格当参数分隔符。如果你的adb.exe放在C:\Program Files\platform-tools\adb.exe不包引号系统会把C:\Program当成程序路径然后报错。这个坑我踩过不止一次尤其是把平台工具放在标准安装目录时路径里几乎必然带空格。再看wait until completion?这个布尔输入。勾选True时LabVIEW会一直等待命令执行完毕然后把标准输出完整返回不勾选时命令在后台执行这个VI立刻返回但此时standard output大概率是空字符串因为子进程还没跑完你就去读了。我自己的建议是在绝大多数自动化测试场景下都要勾选True否则你无法确定设备操作是否已经生效。只有个别场景比如用adb shell am start去启动一个应用后不想等它返回或者想同时发多个命令时才用False。还有一个不太起眼但很实用的引脚是working directory。adb的pull、push命令如果不写绝对路径就会以当前进程的工作目录为基准来解析路径。问题是LabVIEW运行时的工作目录不一定是你以为的那个目录它可能和VI所在目录不一致。所以我在做会自动生成文件的命令时都会在working directory里显式指定一个路径比如D:\TestData\Screenshots这样后续管理文件会清晰很多。继续说use standard error?。这个输入设为True时VI会输出standard error信息设为False标准错误内容会被丢弃和标准输出混在一起实际上接线的习惯是默认True打开因为adb命令在出错时的很多诊断信息都走stderr。你在判断一条命令是否成功时不能只看exit code是0就完了还得看看stderr里有没有“error:”之类的关键字。再提一个很多人忽略的细节System Exec.vi内部是通过系统API执行程序的不是通过cmd.exe所以它本身不会弹出一个黑窗口。但如果你图省事把命令写成cmd /c adb devices那就会闪一下cmd窗口。在产线上窗口闪烁可能影响别的联机程序焦点我的建议是能直接调adb.exe就尽量不要包一层cmd。确实需要cmd的管道、重定向语法时再单独处理。这么接线之后一次adb shell操作的最小LabVIEW实现就是一个字符串常量输入C:\Android\platform-tools\adb.exe shell input tap 520 900接到System Exec.vi的command linewait until completion?接True输出端接一个字符串显示控件就能看到命令执行后的结果。不过这里有个小问题点击操作通常不返回任何文字标准输出是空的你看不到“成功”的迹象。所以更可靠的判断方式是看exit code只要返回0就说明adb命令被设备正常接收并执行了。5. 输出捕获、超时与乱码让命令结果真正可用执行一条adb命令只是第一步真正让测试程序跑起来还要处理三个绕不开的问题输出怎么解析、超时怎么办、中文乱码怎么解。先说出解析。System Exec.vi返回的standard output是一个整体字符串。比如执行adb devices返回的是类似这样的多行文本List of devices attached ABC12345678 device emulator-5554 device要把设备序列号提取出来最简单的方式是按行拆分。LabVIEW里可以用“匹配模式”配合循环逐行截取也可以用函数选板“字符串”分类里的“替换行结束符”先把\r\n统一成\n再用“扫描字符串”按格式读。我在实际程序里更常用一种思路把整段输出当成一个字符串数组来看待先按\n拆行然后逐个判断每行内容找到包含device关键字的行再截取空格之前的字段。虽然听起来原始但在LabVIEW里这种做法最直观、最好调试。再拆一个常见的需求从adb shell wm size的输出里解析分辨率。它的输出类似Physical size: 1080x2340可以用“匹配模式”配合正则表达式\dx\d把数字直接抓出来也可以用“搜索替换字符串”把非数字内容去掉这样就能拿到1080x2340再按x拆成两个数值。第二个问题是超时。System Exec.vi没有超时输入这是我前面提到过的一个硬伤。实测中如果设备突然掉线或者adbdAndroid调试守护进程卡住一条adb shell input tap可能卡几十秒甚至更久上位机界面会直接假死。我的解决办法分三层。第一层是预防在每次执行关键操作之前先跑一条轻量级命令比如adb devices检查设备状态确认为device后再往下走。设备不在线就直接报错不要等真正卡住才发现。第二层是隔离把每条adb命令放到单独的子VI里用“启动异步调用”的方式在另一个线程执行调用方通过“等待异步调用Wait On Async Call”节点并设置超时时间。如果超过设定值比如15秒还没有返回就直接放弃这次调用在主流程中标记超时错误。这个方案可以解决System Exec没有超时的问题代价是程序结构复杂一点。第三层是升级如果项目对可靠性要求很高就换用.NET Process方式直接调用WaitForExit(毫秒数)来限制命令执行时间。这是最干净、最可控的解决方案。我在后面的实战案例中实际上就是混合使用的普通命令走System Exec.vi重要且容易挂死的命令走.NET Process。第三个问题是中文乱码。adb输出通常是UTF-8编码但System Exec.vi在中文Windows系统上默认按GBK代码页解释字节流所以一旦命令输出包含中文比如adb shell getprop ro.product.model返回“小米平板”之类的中文名称LabVIEW字符串里就会变成乱码。处理办法比较实用的有两个。第一个办法是重定向到文件把命令写成C:\...\adb.exe shell getprop ro.product.model D:\out.txt命令执行完后用LabVIEW二进制读文件方式把文件内容读出来再通过“字节数组转字符串”节点把编码参数设为UTF-8这样中文就不会乱。这个办法不需要任何额外库兼容性很好。第二个办法是直接用.NET Process方式执行命令把StandardOutput流的编码设成UTF-8再读取。设置方法是用.NET的StreamReader构造函数指定Encoding.UTF8代码量多一点但不需要临时文件。我在实际测试程序里用的基本都是这个方式。6. 那些年我踩过的adb调用坑聊完了常规操作这一章专门讲讲坑。adb本身是一个成熟的工具但和LabVIEW组合使用时有一些坑是初学者很难排查的我把高频的几个列出来。第一个坑设备显示unauthorized。LabVIEW里命令执行后返回error: device unauthorized. Please check the confirmation dialog on your device.这种情况十有八九是手机上弹了“允许USB调试吗”的窗口没人点确认。它特别容易出现在测试员换了电脑、USB调试授权被重置、或者设备系统更新之后。排查方法很简单用adb devices看状态列如果是unauthorized而不是device基本就是这个原因。解决办法是人工在设备上点允许或者在测试流程中增加一条提示执行前检查设备状态状态不对就让操作员去设备上确认弹窗。第二个坑adb server的5037端口被占用。Windows下多个工具可能同时启动adb server争抢端口就会导致adb命令很慢甚至报错。典型现象是第一条命令执行时要等好几秒或者提示cannot bind tcp:5037。解决办法是在LabVIEW程序初始化时先执行一次adb kill-server再执行adb start-server强制重启adb server。注意这一个操作本身也会耗时几秒放在初始化阶段做比较合适不要放在测试循环里。第三个坑路径里的空格。前面提过adb.exe的完整路径要用双引号包起来。这里更隐蔽的情况是如果被测设备serial里包含特殊字符很少见或者命令参数里带了路径比如adb pull /sdcard/s.png D:\My Documents\img.png目标路径的空格也需要单独包引号。我在LabVIEW里是统一规定凡是路径参数在拼接命令字符串时一律用双引号包裹宁可多包也不能漏。第四个坑多设备时没有指定serial。测试台上如果同时插了两台设备adb shell直接执行会报more than one device/emulator。解决办法是先用adb devices拿到所有在线设备的serial列表然后在后续所有命令里都插入-s 设备序列号。LabVIEW端要做的就是一个简单的字符串拼接C:\...\adb.exe -s ABC12345678 shell input tap 520 900。这个习惯要从第一天就养成不要等插了第二台设备才发现程序全部报错。第五个坑input text不支持中文。我最早做自动化输入时想直接用adb shell input text 用户名输入中文结果发现输入框里全是乱码或者直接没反应。后来才弄明白input text命令只兼容ASCII字符中文必须走别的方案。如果确实有中文输入需求一个相对靠谱的方向是安装ADBKeyboard这个输入法插件通过广播把文本传给输入法再上屏不过这属于USB调试权限之外的扩展方案本文就不展开讲了。大多数产测场景里我会优先要求被测App支持英文或数字输入规避这个问题。第六个坑长时间运行后的资源泄漏。这是最隐蔽的坑短时间测试基本发现不了。用.NET Process方式写的老化测试如果每跑一次命令都新建一个Process对象跑完不释放几万次循环之后系统的句柄数会一路飙升最终导致LabVIEW变慢、界面卡顿甚至整个系统无响应。解决办法是Process对象用完以后一定要调用Dispose()或者用“Close Main Window”先把主窗口释放更省心的做法是复用同一个Process对象只重置参数而不是频繁创建销毁。7. 实战用LabVIEW驱动Android设备跑一轮老化测试前面都是基础知识和避坑指南这一章完整走一个实际项目。这个项目的背景是公司一款Android POS机要做8小时连续点击老化测试要求每天自动点击收款主界面上的“确认收款”按钮一万次每隔100次截图一次判断按钮是否出现异常比如界面卡死、弹窗遮挡。整个测试由LabVIEW上位机控制完全无人值守。先看测试流程设计。我把它拆成了四个阶段初始化阶段检查adb服务、检查设备在线状态、获取设备型号和屏幕分辨率、通过分辨率计算“确认收款”按钮的坐标、清空本次测试历史数据、创建日志文件和截图目录。测试循环阶段每2秒执行一次input tap点击按钮每次点击后等待500ms执行screencap截图并pull到PC对截图做模板匹配判断按钮区域是否正常记录点击序号、时间戳、exit code、匹配结果。异常处理阶段如果连续3次截图判图异常停止测试并在上位机界面报警如果是单次命令超时或者adb拉取失败记录错误日志并重试一次超过5次则断定为设备异常直接终止测试。收尾阶段生成测试报告统计总点击次数、失败次数、异常截图路径、平均每次点击耗时保存到TDMS文件和CSV文件中。关键逻辑上有几个点值得单独说一下。第一个是节拍控制。我在循环里没有用简单的“等待ms”函数而是用“已用时间”这个VI记录上一轮循环的结束时间再计算下一轮应该开始的绝对时间。原因是adb命令的耗时是不稳定的截图大图时pull可能花1秒网络波动时可能花3秒如果用固定延时等待循环周期会越跑越偏8小时下来误差会非常大。用绝对时间的算法无论单条命令耗时多少每分钟的点击次数都能保持基本稳定。第二个是截图文件命名。文件名的格式我建议用时间戳img_20250501_101530_001.png其中最后三位是循环序号。这样就算测试中途异常退出后续排查时也能根据文件名快速定位到对应的循环序号和历史日志。第三个是图像判断方式。如果项目里已经装了NI Vision模块可以直接用IMAQ Create、IMAQ ReadFile和IMAQ Match Pattern实现模板匹配先在测试前手动截一张正常界面的图裁出按钮区域作为模板然后在每次截图后做匹配用匹配分数和位置偏差判断界面是否正常。如果没有视觉模块退而求其次可以用LabVIEW基础函数读取图片的特定像素颜色比较按钮区域的颜色是否符合预期。前者更可靠后者更轻量看项目预算和精度要求取舍。第四个是循环里的错误隔离。老化测试跑8小时中间一定会出现单次adb命令失败的情况可能是USB接触不良也可能是设备休眠导致调试通道短暂断开。所以我在循环里做了重试机制单条命令失败后先执行一次adb reconnect或者adb devices检查设备状态再重试该命令。重试次数不超过5次超过就判定为设备需要人工介入。这个机制把偶发故障和真正的大故障区分开了能显著降低误报警的频率。这个项目跑下来最终的效果是8小时无人值守完成了14400次点击截图跳过了约140张用于人工复核整个测试过程没有人工干预发现了一次界面卡死和一次弹窗遮挡的异常。相比之前人工点击测试效率提升了大概5倍而且测试过程的可追溯性完全不同——每一次点击、每一张截图、每一个exit code都在日志里出了问题可以直接定位到是在哪一次点击后出现的。8. 把adb调用封装成子VI一个工程用到老做到这一步我建议你把前面所有adb调用逻辑封装成子VI而不是在每一个测试程序里都把System Exec.vi拖出来接线。封装的好处不用多说命令拼接逻辑统一管、错误处理统一管、输出解析统一管后续新项目直接拖VI接线就行不用重新踩一遍坑。我的通用子VI命名为ADB Execute.vi输入输出定义如下输入serial字符串可空空表示单设备、command字符串要去设备上执行的shell命令、timeout_msI32默认10000、wait for completion布尔默认True、error in错误簇。输出stdout字符串数组按行拆分后的输出、exit codeI32、error out错误簇。内部处理流程是这样的首先检查error in错误簇如果上游已经有错误直接跳过命令执行把错误传递给error out。这个习惯非常重要否则一旦流程出错后续命令还会继续执行可能引发连锁问题。拼接命令字符串。如果serial不为空就插入-s serial如果为空就不加。这样单设备测试和多设备测试用同一个VI不用各写一套。选择底层执行方式。我默认用System Exec.vi短命令执行够用但对那些可能长时间无响应的命令比如wait-for-device我会额外封装一个ADB Execute with Timeout.vi内部用.NET Process实现并启用超时。处理输出。System Exec.vi返回的字符串按行拆分成stdout数组去掉首尾空行。这一步之所以在子VI里做是因为上层程序每次都要做统一封装可以减少重复代码。判定结果。如果exit code不等于0把标准错误和退出码拼成一个错误描述塞进error out错误簇。注意不要只报“命令执行失败”要把具体命令、退出码、标准错误内容一起带出来否则现场排查时会觉得两眼一抹黑。在这个通用子VI的基础上再封装几个高频动作VI每个都短小精悍ADB Tap.vi输入x、y坐标内部拼装input tap x y。ADB Swipe.vi输入起点、终点、时长内部拼装input swipe ...。ADB Keyevent.vi输入键码内部拼装input keyevent ...。ADB Screenshot.vi输入保存路径内部先执行screencap再执行pull最后删掉设备端的临时文件。ADB Get Device Info.vi内部执行getprop并解析关键字段输出设备型号、系统版本、分辨率。有了这组VI新项目的测试代码基本上就是搭积木了。比如要让设备点击某个坐标然后截图流程VI里就是三个节点的连线ADB Tap.vi- 延时500ms -ADB Screenshot.vi每个节点都带错误簇传递哪一步出错一目了然。最后再分享一个实际项目里非常有用的小设计我在上位机界面里固定留了一个“命令行调试面板”一个字符串输入框加一个执行按钮用户可以输入任意adb命令直接执行并显示输出。开发调试阶段我需要测试某个新命令是否有效不用改主程序直接在面板里敲一遍产线出问题时维护人员也可以用这个面板快速诊断设备状态。这个小设计看起来不起眼但在现场排障时帮了很大的忙。从LabVIEW调用adb shell这件事延伸开去你会发现它本质上是一个“把系统能力借过来”的思维LabVIEW擅长流程控制和数据处理Android系统自带强大的自动化调试接口两者通过一行命令行拼接就能连通。做测控上位机的人如果能熟练驾驭这种跨工具的交互方式很多看起来复杂的自动化需求都会变得很简单。我自己现在做Android设备相关的LabVIEW项目第一个考虑的就是能不能用adb shell解决而不是去动业务App的代码。