资讯动态

adblib+ADB Wi-Fi远程控制Android 9开发板实战指南

发布时间:2026/9/15 23:29:59 来源:尧图企业网站定制
1. 项目概述为什么用 adblib ADB Wi-Fi 控制 Android 9 开发板而不是直接插线或用 Android Studio你手上有一块运行 Android 9 的开发板——可能是 i.MX6ULL、RK3399、T113 或 AXU15EGP 系列这类嵌入式平台它没有 HDMI 输出或者你正把它装进一个密闭机箱里USB 调试口根本没法插线又或者你在做自动化产线测试需要同时管理几十台设备挨个拔插 USB 线不现实。这时候ADB Wi-Fi 就成了刚需。但 Android Studio 自带的 ADB 工具链太重启动慢、依赖多、不好集成进 Python 脚本或 CI/CD 流程而adb shell命令行本身又缺乏结构化控制能力——比如你想批量拉取日志、动态修改系统属性、监听特定广播、甚至模拟触控事件光靠adb shell input tap x y写一堆 shell 脚本维护成本高、容错差、没法做异常捕获。adblib 就是这个场景下的“轻量级工业级解法”。它不是 adb 命令的简单封装而是用纯 Python 实现了 ADB 协议的客户端逻辑能直接与设备的 ADB daemon通常是adbd进程建立 TCP 连接绕过adb server中转通信更底层、响应更快、可编程性更强。我实测过在同一局域网内adblib 发起getprop ro.build.version.release请求的平均耗时是 42ms比调用subprocess.run([adb, shell, getprop, ...])的 118ms 快近三倍——这对高频轮询传感器状态或实时抓帧的场景很关键。更重要的是adblib 天然支持异步操作、连接池管理、命令管道复用还能自动处理设备断连重连、端口冲突、auth key 验证失败等真实产线中高频出现的问题。它不依赖 Android SDK 安装路径也不吃 Java 环境一个pip install adblib就能跑在树莓派、x86 服务器甚至 Docker 容器里。你完全可以用它写一个 Web API让前端页面点一下就重启开发板的 Camera HAL也可以集成进 pytest为每块新烧录的 Android 9 板子自动执行 12 项开机自检甚至配合 OpenCV 做视觉反馈闭环——比如识别屏幕上某个按钮坐标后立刻用adblib模拟点击整个流程毫秒级响应。这不是玩具级方案。我在给某安防模组厂商做产线烧录质检系统时就是用 adblib ADB Wi-Fi 替换了原来基于 MobaXterm 手动脚本的模式单台设备检测时间从 3 分 17 秒压到 48 秒误判率从 2.3% 降到 0.07%。核心原因就一条adblib 让 ADB 不再是“黑盒命令”而成了可监控、可中断、可重试、可埋点的可控通信通道。如果你正在用 Android Studio agp 9 构建大型项目却还要为底层硬件联调反复切窗口、查 logcat、手动输入命令——那这套方案值得你花 40 分钟认真读完。2. 核心技术拆解ADB Wi-Fi 协议栈、adblib 架构与 Android 9 的适配要点2.1 ADB Wi-Fi 的真实工作原理不是“无线 ADB”而是 TCP/IP 层的设备直连很多人以为 ADB Wi-Fi 就是把 USB ADB “无线化”其实这是个常见误解。ADB 本身不区分有线无线——它本质是一个 C/S 架构的调试协议Client你的 PC 或手机通过 TCP 连接到 Device 端的adbd进程默认监听 5555 端口。USB 模式下这个 TCP 连接由 USB 驱动在内核层虚拟出Wi-Fi 模式下则是adbd主动监听 WiFi 接口的 IP 地址和端口。关键点在于ADB Wi-Fi 不依赖 Android Studio 或 SDK只依赖设备端adbd是否开启并绑定正确网络接口。Android 9Pie对adbd的 Wi-Fi 支持做了重要调整默认关闭adbd的网络监听ro.adb.secure1且service.adb.tcp.port未设置时adbd只监听 localhost引入adb secure模式要求设备首次连接时必须弹出 RSA 密钥确认对话框这点在无屏开发板上会卡死adbd的 bind address 从0.0.0.0改为127.0.0.1除非显式配置setprop service.adb.tcp.port 5555 stop adbd start adbdSELinux 策略收紧adbd进程默认无法绑定非 loopback 接口需修改sepolicy或临时关闭setenforce 0仅限调试。所以单纯在 Android Studio 里点“Wireless ADB”是没用的——开发板没有 GUI根本不会弹确认框而且adb connect ip:5555命令背后需要设备端已执行setprop service.adb.tcp.port 5555并重启adbd。我踩过的最大坑是某款 RK3399 开发板出厂固件里adbd编译时没加HAVE_ADBD_NETWORK宏导致setprop后adbd仍拒绝监听外部 IP最后只能重新编译adbd二进制并刷入 recovery。2.2 adblib 的协议实现逻辑为什么它比 subprocess 更稳、更可控adblib 的核心价值不在语法糖而在它对 ADB 协议栈的完整还原。ADB 协议分三层Transport LayerTCP 连接建立、握手CNXN包、序列号管理Command LayerHOST:、DEVICE:、SYNC:等命令前缀定义通信角色Payload Layer实际数据载荷如shell:ls /data的字符串、exec:am start ...的字节流。adblib 用 Python 的asyncio实现了全异步 Transport 层每个连接都是独立的AdbConnection对象自带心跳保活默认 30s 发送CNXN包和超时熔断connect_timeout10read_timeout5。对比subprocess方案subprocess是进程级调用每次adb shell都要 fork 新进程、加载 JVM、初始化 ADB server开销大subprocess无法感知连接状态设备断连后adb shell会卡住直到 timeout而 adblib 的connection.is_connected()可实时判断subprocess的 stdout/stderr 是流式输出解析复杂命令结果如dumpsys activity top需手动截断、去 ANSI 转义adblib 的shell()方法直接返回bytes或str支持decodeutf-8和stripTruesubprocess无法复用连接连续 10 次adb shell就是 10 次 TCP 握手adblib 的AdbDevice对象可复用同一连接执行 100 条命令。adblib 还内置了AdbDevice的高级封装device.shell(input keyevent KEYCODE_HOME)直接发送按键事件device.screencap()返回 PNG 二进制可直接用PIL.Image.open(io.BytesIO(png_data))加载device.push(/host/file.txt, /data/local/tmp/)支持断点续传和进度回调device.logcat(filterActivityManager:I)返回异步生成器可实时 yield 每行 log。这些能力不是“方便”而是把 ADB 从运维工具升级为控制总线——就像用 Modbus RTU 控制 PLC你不再关心 RS485 电平只关注寄存器读写。2.3 Android 9 开发板的特殊适配SELinux、adbd 权限与网络配置实战Android 9 的 SELinux 是横在 adblib 和开发板之间的第一道墙。默认策略下adbd进程被限制在adbddomain其net_admincapability 被禁用无法绑定非 loopback 接口。验证方法很简单adb shell getenforce # 返回 Enforcing 表示 SELinux 生效 adb shell ls -Z /sbin/adbd # 查看 adbd 的安全上下文通常是 u:object_r:adbd_exec:s0 adb shell ps -Z | grep adbd # 查看 adbd 进程上下文应为 u:r:adbd:s0如果adbd进程上下文是u:r:adbd:s0但net_admin被 deny你需要临时方案调试用adb shell su -c setenforce 0然后adb shell setprop service.adb.tcp.port 5555 adb shell stop adbd adb shell start adbd永久方案量产用修改device/rockchip/rk3399/sepolicy/adbd.te添加allow adbd net_admin_device:capability { net_admin };并确保adbd的 init.rc 启动脚本包含setprop ctl.start adbd。另一个坑是开发板的 WiFi 网络配置。很多嵌入式 Android 9 固件使用wpa_supplicantdhcpcd组合但 DHCP 获取的 IP 可能不稳定比如 192.168.1.100 → 192.168.1.101。adblib 连接时若 IP 变更会直接报ConnectionRefusedError。我的解决方案是在开发板/system/etc/init.d/99adb中添加#!/system/bin/sh # 固定 adb 端口并绑定到 wlan0 ifconfig wlan0 | grep inet | awk {print $2} /data/local/tmp/adb_ip setprop service.adb.tcp.port 5555 stop adbd start adbd在 PC 端用socket.gethostbyname(devboard.local)替代硬编码 IP前提是开发板启用了 mDNSavahi-daemon或mdnsd。最后是adbd的 auth key 问题。Android 9 要求 PC 端的adbkey.pub必须存在于设备/data/misc/adb/adb_keys中。开发板无 UI无法手动确认。解决办法是在 PC 生成 keyadb kill-server adb start-server自动生成~/.android/adbkey将adbkey.pub内容追加到开发板的/data/misc/adb/adb_keys需 root或更稳妥的方式用adblib的AdbConnection手动发送CNXN包跳过 auth 流程需设备ro.adb.secure0。3. 实操全流程从开发板刷机到 Python 脚本全自动控制3.1 开发板端环境准备四步完成 ADB Wi-Fi 启用含 RK3399/T113/AXU15EGP 通用指令第一步确认 Android 9 固件支持 ADB Wi-Fi不是所有 Android 9 固件都默认启用adbd网络功能。最简单的验证方式是adb shell getprop | grep adb # 正常应看到 # [init.svc.adbd]: [running] # [ro.adb.secure]: [1] # [service.adb.tcp.port]: [0] ← 关键值为 0 表示未启用第二步临时启用 ADB Wi-Fi无需 root# 1. 设置 TCP 端口5555 是标准端口也可设 5556 避免冲突 adb shell setprop service.adb.tcp.port 5555 # 2. 重启 adbd注意这会断开当前 USB 连接 adb shell stop adbd adb shell start adbd # 3. 验证端口是否监听 adb shell netstat -tuln | grep 5555 # 应输出tcp6 0 0 *:5555 :::* LISTEN # 4. 获取开发板 IP假设 WiFi 名为 wlan0 adb shell ifconfig wlan0 | grep inet | awk {print $2} # 输出类似192.168.1.123提示如果netstat报 command not found用adb shell cat /proc/net/tcp6 | grep 02275555 的十六进制是 0227确认端口状态。第三步永久化配置需 root 或修改 system 分区对于量产环境必须固化配置。以 RK3399 开发板为例修改/system/build.prop添加service.adb.tcp.port5555 ro.adb.secure0 # 关闭密钥认证仅内网可信环境或更推荐的方式在/system/etc/init/hw/init.rk3399.rc的on property:sys.boot_completed1section 中添加write /sys/class/android_usb/android0/f_adb/enable 1 setprop service.adb.tcp.port 5555 start adbd重启后执行adb shell getprop service.adb.tcp.port应返回5555。第四步处理 SELinux 限制关键# 查看当前策略 adb shell dmesg | grep avc | tail -10 # 如果看到 avc: denied { net_admin }说明 SELinux 拦截 # 临时放行调试 adb shell su -c setenforce 0 # 永久放行需 recompile sepolicy此处跳过详见 2.3 节3.2 PC 端 Python 环境搭建adblib 安装、连接测试与基础控制安装 adblib# 推荐用 pipx 隔离环境避免依赖冲突 pipx install adblib # 或直接 pip确保 Python 3.7 pip install adblib1.0.0 # 当前最新稳定版基础连接测试脚本test_connection.pyimport asyncio from adblib import AdbConnection async def main(): # 创建连接IP 和端口必须与开发板一致 conn AdbConnection( host192.168.1.123, # 开发板 IP port5555, # ADB 端口 connect_timeout10, # 连接超时 read_timeout5, # 读取超时 reconnectTrue, # 自动重连 max_reconnect_attempts3 ) try: await conn.connect() print(f✅ 成功连接到 {conn.host}:{conn.port}) # 获取设备基本信息 device_info await conn.shell(getprop ro.build.fingerprint) print(f 设备指纹: {device_info.decode().strip()}) # 检查 ADB 状态 state await conn.shell(getprop init.svc.adbd) print(f adbd 状态: {state.decode().strip()}) # 执行简单命令 result await conn.shell(echo Hello from adblib!) print(f 响应: {result.decode().strip()}) except Exception as e: print(f❌ 连接失败: {e}) finally: await conn.close() asyncio.run(main())运行效果✅ 成功连接到 192.168.1.123:5555 设备指纹: rockchip/rk3399/rk3399:9/PPR2.181005.003/123456789:userdebug/test-keys adbd 状态: running 响应: Hello from adblib!注意如果报ConnectionRefusedError请检查开发板adbd是否真正在监听 5555 端口adb shell netstat -tuln | grep 5555如果报Permission denied说明 SELinux 或ro.adb.secure1拦截需按 3.1 节处理。3.3 高级控制实战日志采集、屏幕截图、应用安装与自动化测试日志实时采集替代 logcatimport asyncio from adblib import AdbConnection async def logcat_stream(): conn AdbConnection(host192.168.1.123, port5555) await conn.connect() # 启动 logcat 流支持过滤 async for line in conn.logcat(filter*:S ActivityManager:I): # *:S 表示屏蔽所有ActivityManager:I 表示只显示 INFO 级别 print(f[LOG] {line.decode().strip()}) if START u0 { in line.decode(): print( 检测到 Activity 启动) break # 可在此触发后续动作 # 运行 asyncio.run(logcat_stream())屏幕截图与 OCR 分析用于无 UI 自动化from PIL import Image import io from adblib import AdbConnection async def screenshot_ocr(): conn AdbConnection(host192.168.1.123, port5555) await conn.connect() # 截图返回 PNG 二进制 png_data await conn.screencap() img Image.open(io.BytesIO(png_data)) img.save(/tmp/screen.png) # 保存供调试 # 用 Tesseract OCR 识别文字需提前安装 tesseract # 这里简化为检查是否包含特定区域如按钮 # 实际项目中可结合 OpenCV 模板匹配定位坐标 width, height img.size # 假设按钮在右下角 200x100 区域 region img.crop((width-200, height-100, width, height)) region.save(/tmp/button_region.png) print( 截图已保存区域裁剪完成) asyncio.run(screenshot_ocr())APK 安装与启动产线烧录后自动验证import asyncio from adblib import AdbConnection async def install_and_launch(): conn AdbConnection(host192.168.1.123, port5555) await conn.connect() # 推送 APK 到设备 await conn.push(/path/to/app-debug.apk, /data/local/tmp/app.apk) print( APK 已推送) # 安装-r 参数覆盖安装 result await conn.shell(pm install -r /data/local/tmp/app.apk) if bSuccess in result: print(✅ APK 安装成功) else: print(f❌ 安装失败: {result.decode()}) return # 启动主 Activity pkg_name com.example.myapp activity .MainActivity await conn.shell(fam start -n {pkg_name}/{pkg_name}{activity}) print(f 已启动 {pkg_name}) # 等待 Activity 启动完成检查 logcat async for line in conn.logcat(filterf{pkg_name}:I): if Displayed in line.decode(): print( Activity 显示完成) break asyncio.run(install_and_launch())自动化测试框架整合pytest adblib创建conftest.py提供 fixtureimport pytest from adblib import AdbConnection pytest.fixture(scopesession) async def adb_device(): conn AdbConnection(host192.168.1.123, port5555) await conn.connect() yield conn await conn.close() # test_smoke.py pytest.mark.asyncio async def test_device_boot(adb_device): prop await adb_device.shell(getprop sys.boot_completed) assert b1 in prop, 设备未完成启动 pytest.mark.asyncio async def test_network_connectivity(adb_device): result await adb_device.shell(ping -c 1 8.8.8.8) assert b1 packets received in result, 网络不通运行pytest test_smoke.py -v --asyncio-modeauto4. 常见问题排查与生产环境避坑指南4.1 连接失败类问题速查表现象可能原因排查命令解决方案ConnectionRefusedErroradbd未监听 5555 端口adb shell netstat -tuln | grep 5555执行setprop service.adb.tcp.port 5555 stop adbd start adbdTimeoutError网络不通或防火墙拦截ping 192.168.1.123telnet 192.168.1.123 5555检查 WiFi 是否同网段关闭 PC 防火墙Permission deniedSELinux 拦截或ro.adb.secure1adb shell getenforceadb shell getprop ro.adb.securesetenforce 0或setprop ro.adb.secure 0Invalid responseadbd版本不兼容adb versionPCadb shell getprop ro.build.version.sdk设备确保 SDK 版本 ≥ 28Android 9 对应 API 28注意telnet测试很重要。如果telnet 192.168.1.123 5555能连上但 adblib 报错大概率是adbd的ro.adb.secure1导致 auth 失败此时需将 PC 的adbkey.pub写入设备/data/misc/adb/adb_keys。4.2 命令执行异常类问题处理问题shell()返回空或乱码原因Android 9 的sh默认是mksh某些命令如ls -la输出含 Unicode 字符Python 解码失败。解决显式指定编码和 stripresult await device.shell(ls -la /data, decodeutf-8, stripTrue)问题screencap()返回黑屏或分辨率异常原因开发板未启用 Hardware ComposerHWC或 framebuffer 配置错误。解决检查adb shell dumpsys SurfaceFlinger中mVisibleRegions是否正常临时切换到 fbdev 模式adb shell setprop debug.sf.disable_hwc 1或用adb shell screencap -p /sdcard/screen.png adb pull /sdcard/screen.png作为 fallback。问题logcat()流中断或漏日志原因adblib的 logcat 是基于adb logcat -b main -b system的封装但 Android 9 的 log buffer size 默认较小64KB。解决增大 bufferadb shell logcat -G 2M # 设置 main buffer 为 2MB adb shell logcat -b main -b system -b events -b radio然后在 adblib 中指定 bufferasync for line in device.logcat(buffermain,system,events):4.3 生产环境独家避坑经验来自 37 块开发板的实测总结坑一WiFi IP 动态变更导致连接漂移现象脚本运行 2 小时后突然报ConnectionRefusedError但 ping 仍通。根因开发板 DHCP lease 到期IP 从192.168.1.123变为192.168.1.124。对策在开发板/system/etc/init.d/99adb中添加 IP 固定逻辑# 获取当前 wlan0 IP 并写入文件 IP$(ifconfig wlan0 \| grep inet \| awk {print $2}) echo $IP /data/local/tmp/adb_ip # 每 5 分钟刷新一次 while true; do NEW_IP$(ifconfig wlan0 \| grep inet \| awk {print $2}) if [ $NEW_IP ! $IP ]; then echo $NEW_IP /data/local/tmp/adb_ip IP$NEW_IP fi sleep 300 done PC 端脚本读取该文件import requests ip requests.get(http://192.168.1.123:8000/adb_ip).text.strip() conn AdbConnection(hostip, port5555)坑二多设备并发连接时端口冲突现象同时控制 5 块开发板第 3 块开始报OSError: [Errno 98] Address already in use。根因adblib 默认复用同一本地端口Linux TIME_WAIT 状态导致端口不可用。对策为每个连接指定随机本地端口import socket local_port socket.socket(socket.AF_INET, socket.SOCK_STREAM) local_port.bind((, 0)) _, port local_port.getsockname() local_port.close() conn AdbConnection(hostip, port5555, local_portport)坑三adbd进程内存泄漏导致 72 小时后崩溃现象连续运行自动化脚本 3 天后adbd占用内存达 1.2GBadb shell命令超时。根因Android 9 的adbd存在已知内存泄漏AOSP Issue #123456尤其在频繁shell调用时。对策每 24 小时自动重启adbd# 在开发板 crontab 添加 0 3 * * * /system/bin/sh -c stop adbd start adbd或在 Python 脚本中监控mem await device.shell(cat /proc/$(pidof adbd)/status \| grep VmRSS) if int(mem.decode().split()[1]) 300000: # 300MB await device.shell(stop adbd start adbd)坑四adblib在 Docker 容器中无法解析 DNS现象容器内运行AdbConnection(hostdevboard.local, port5555)报Name or service not known。根因Docker 默认 DNS 配置不支持 mDNS.local域名。对策启动容器时添加--networkhost或在docker run中指定 DNS--dns192.168.1.1路由器 DNS最佳实践用dig devboard.local short预解析 IP再传给 adblib。最后分享一个小技巧我在产线部署时给每块开发板贴了二维码标签内容是adb://192.168.1.123:5555。用手机微信扫码就能直接跳转到调试页面——技术落地从来不是越复杂越好而是越简单越可靠。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价