C++ 模拟器自动化开发记录:由常驻 Shell 管道污染引起的截图错位问题
在开发 Android 模拟器自动化工具的 C++ 版本时,遇到了一个隐蔽的问题:截图功能在单独测试时完全正常,但在执行点击操作后,紧接着进行截图会导致图像数据错位或花屏。本文记录了该问题的排查过程及根本原因。
1. 现象描述
C++ 端通过自定义的 SubProcess 类 fork 了一个常驻的 sh 子进程,通过管道与子进程进行通信。主程序向管道写入命令,并从管道读取返回数据。
在 main 函数中循环调用截图函数(底层执行 screencap),获取的 1080x1920 RGBA 图像数据完全正常。然而,当加入模拟点击的 tap 函数后,只要调用一次 tap,随后的截图就会出现画面错位、顶部出现彩色条纹等典型的数据偏移现象。
2. 排查过程
首先怀疑的是 C++ 内存管理问题,如缓冲区越界、指针悬空或多线程竞争。经过排查,get_screenshot 返回的是类成员内部的缓冲区指针,生命周期正确,且当时并未引入多线程。
随后通过二分法定位到触发的条件:只要调用了 tap 函数,后续截图必定错位。
观察 tap 函数的实现,其原理是通过 printf 命令将构造好的二进制指令写入临时文件,最后使用 dd 命令将文件内容写入 /dev/input/eventX 设备节点。
关键点在最后一条命令:
dd if=/data/local/tmp/touch.bin of=/dev/input/event1 bs=24
在 Linux/Android 环境下,dd 命令执行完毕后,默认会在标准输出打印一条统计信息,例如:
11+0 records in
11+0 records out
264 bytes (264 B) copied, 0.000165 s, 1.5 M/s
3. 根本原因
由于使用的是常驻 Shell 进程,所有命令(包括 tap 的底层指令和 screencap)共用同一个标准输入输出管道。
当 dd 命令执行完毕后,它打印的那百余字节的文本信息残留在管道的输出缓冲区中,程序并未将其读取。
当程序接下来调用 screencap 并执行 read() 读取 8MB 图像数据时,底层实际先读到的是上一轮 dd 遗留的文本字符,随后才是真正的图像二进制数据。这导致整个图像数据发生了约百余字节的物理偏移,最终在视觉上表现为花屏和错位。
4. 解决方法
问题的核心在于非业务输出污染了数据管道。解决方法是给不需要返回结果的命令追加标准输出和标准错误的重定向,将其丢弃。
修改 dd 命令:
dd if=/data/local/tmp/touch.bin of=/dev/input/event1 bs=24 > /dev/null 2>&1
修改后,dd 的统计信息不再写入管道。后续 screencap 读取时,管道里只有纯粹的图像数据。问题解决。
6. 总结
在使用常驻 Shell 进程进行自动化交互时,必须严格管理管道的数据流。对于不需要解析返回结果的命令(如文件操作、输入事件注入等),应统一添加 > /dev/null 2>&1 静默处理,防止命令的标准输出回显污染后续读取的二进制数据流。