1. Android 模拟器抓包与自动化调试链路为什么总在最后一步断掉
Android 模拟器抓包与自动化集成方案,说白了就是让「看得见流量」和「点得动界面」这两件事在同一台虚拟设备上同时成立。它适合谁?适合需要批量调试接口的移动开发、测试开发,以及要把调试流程交接给同事的团队。能做什么?把模拟器代理、ADB 端口转发、统一 Key 接入串成一条可复现的链路,抓到的请求能直接喂给自动化脚本回放。
我见过太多人卡在同一个地方:mitmproxy 里流量哗哗地刷,脚本却连不上接口;或者脚本能跑,但每次换台机器就要重新配一遍 Key 和代理。问题不在工具本身,而在于抓包环境和自动化环境是两套割裂的配置。模拟器里装了证书、设了代理,主机上的脚本却还在用另一套地址和凭证,中间靠手工复制粘贴维持,一旦要交接就全乱。
这篇就按「模拟器代理配置 → ADB 端口转发 → 统一 Key 接入 → 抓包到脚本回放验证」的顺序走一遍。核心思路是:让模拟器、抓包工具、自动化脚本共用同一份接入配置,Base URL、Key、Model ID 三件套只维护一处。这样调试链路才可复现、可交接,而不是某台机器上的「祖传配置」。
下面所有命令和配置都以 Android Studio Emulator + mitmproxy + ADB 为基础,TaoToken 作为统一接入层。你不需要一次全做完,可以按小节逐步验证。
2. TaoToken 统一 Key 接入前的环境准备与 ADB 调试链路搭建
在讲接入之前,先把模拟器和 ADB 这条链路搭稳。很多人跳过这步直接配 Key,结果报错时根本分不清是网络问题还是凭证问题。
2.1 模拟器与 ADB 基础确认
启动 Android Studio Emulator 后,第一件事是确认 ADB 能看到设备:
adb devices正常输出应该是这样:
List of devices attached emulator-5554 device如果显示offline或列表为空,先别急着往下走。offline通常是模拟器还没完全启动,等系统桌面出来再执行一次;列表为空则检查 ADB 版本和模拟器是否在同一台主机上。我试过在 WSL 里跑 adb 而模拟器在 Windows 主机上,这种情况需要额外做端口转发,建议直接在模拟器所在系统里操作。
确认设备在线后,测试一下 shell 通道:
adb shell getprop ro.build.version.release能返回 Android 版本号,说明 ADB 调试链路是通的。这一步是整个自动化方案的地基,地基不稳后面全是玄学问题。
2.2 模拟器代理指向主机抓包工具
抓包工具跑在主机上,模拟器要把流量导过去。Android 模拟器访问主机有个特殊别名10.0.2.2,等价于主机的127.0.0.1。
在模拟器里进入 Settings > Network & internet > Internet,长按已连接的 Wi-Fi(通常是 AndroidWifi),选择 Modify network,展开 Advanced options,把 Proxy 设为 Manual:
Proxy hostname: 10.0.2.2 Proxy port: 8080保存后,模拟器的 HTTP 流量就会走主机的 8080 端口。这里有个坑:如果你主机上 mitmproxy 监听的是127.0.0.1:8080,模拟器是连不上的,必须让它监听所有网卡:
mitmweb --listen-host 0.0.0.0 --listen-port 80802.3 证书安装与 Android 7.0+ 的信任问题
模拟器浏览器访问http://mitm.it,下载对应平台的证书。安装路径是 Settings > Security > Encryption & credentials > Install a certificate > CA certificate。
注意 Android 7.0 及以上,用户安装的 CA 证书默认不被应用信任,只有系统证书才被信任。对于大多数普通 App 的调试,用户证书够用;如果目标 App 做了证书固定或者你抓的是系统级流量,就需要把证书推到系统目录:
adb root adb remount adb push mitmproxy-ca-cert.pem /system/etc/security/cacerts/ adb shell chmod 644 /system/etc/security/cacerts/mitmproxy-ca-cert.pemadb root和adb remount需要模拟器镜像支持,Google APIs 镜像通常可以,Google Play 镜像可能受限。如果这两条命令报adbd cannot run as root in production builds,说明当前镜像不支持,换 Google APIs 镜像重建 AVD 即可。
2.4 ADB 端口转发打通脚本与模拟器
自动化脚本要控制模拟器,除了adb shell,有时还需要通过端口转发访问模拟器内部服务。比如模拟器里跑了个本地 HTTP 服务在 8000 端口,主机想直接访问:
adb forward tcp:8000 tcp:8000这样主机访问127.0.0.1:8000就等于访问模拟器的 8000 端口。反向的也有:
adb reverse tcp:9000 tcp:9000adb reverse让模拟器访问自己的127.0.0.1:9000时,实际转发到主机的 9000 端口。这个在自动化脚本里特别有用——脚本在主机跑,模拟器里的 App 要回调主机服务时,用 reverse 比配代理更干净。
到这里,模拟器、抓包、ADB 三条线都通了。接下来才是接入统一 Key 的环节。
3. 可复制的 TaoToken 统一 Key 配置与自动化脚本接入
这一节是重点,所有配置都给你可复制的片段。核心原则:Base URL、Key、Model ID 三件套集中管理,抓包工具和自动化脚本读同一份配置。
3.1 获取统一 Key 与接入地址
先到控制台创建 API Key,地址是 https://taotoken.net/console 。创建后复制出来,注意 Key 只显示一次。
接入地址统一用:
Base URL: https://taotoken.net/api模型 ID 根据你要调用的模型填,比如claude-sonnet-4-5这类。三件套凑齐后,下面所有配置都围绕它们展开。
3.2 用 settings 片段管理接入配置
在项目根目录建一个config/settings.json,把三件套写进去:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model_id": "claude-sonnet-4-5", "proxy": { "host": "10.0.2.2", "port": 8080 }, "adb": { "device": "emulator-5554", "forward_port": 8000 } }这个文件是整条链路的单一事实来源。抓包工具读它的 proxy 段,自动化脚本读 base_url、api_key、model_id,ADB 操作读 adb 段。交接时只需要把这个文件(去掉真实 Key)和说明文档一起给同事,对方填上自己的 Key 就能跑。
3.3 自动化脚本读取配置并调用接口
写一个 Python 脚本,从 settings.json 读配置,通过 ADB 操作模拟器,同时调用统一接口:
import json import subprocess import requests with open("config/settings.json", "r", encoding="utf-8") as f: cfg = json.load(f) def adb_shell(command): result = subprocess.run( ["adb", "-s", cfg["adb"]["device"], "shell", command], capture_output=True, text=True ) return result.stdout, result.stderr def call_model(prompt): headers = { "Authorization": f"Bearer {cfg['api_key']}", "Content-Type": "application/json" } payload = { "model": cfg["model_id"], "messages": [{"role": "user", "content": prompt}] } resp = requests.post( f"{cfg['base_url']}/v1/chat/completions", headers=headers, json=payload, timeout=60 ) return resp.json() if __name__ == "__main__": out, err = adb_shell("input tap 500 500") print("tap result:", out, err) result = call_model("用一句话说明当前页面可能是什么") print("model result:", result)注意adb -s后面跟设备号,多设备时避免歧义。base_url拼接/v1/chat/completions是常见的 OpenAI 兼容路径,具体以接入文档为准。
3.4 抓包工具与脚本共用代理配置
mitmproxy 启动时读 settings.json 里的 proxy 段,保证抓包端口和脚本认知一致:
python -c " import json cfg = json.load(open('config/settings.json')) print(f\"mitmweb --listen-host 0.0.0.0 --listen-port {cfg['proxy']['port']}\") " | bash这样代理端口改了,抓包工具和模拟器配置同步改,不会出现「脚本以为走 8080,模拟器实际走 9090」的错位。
配置到这一步,链路已经成型。下一节做一次完整的验证。
4. 从抓包到自动化脚本回放的完整验证请求
验证动作要能同时证明三件事:抓包工具能看到流量、脚本能通过 ADB 操作模拟器、统一接口能正常返回。缺一个都说明链路有断点。
4.1 启动顺序与前置检查
按这个顺序启动,避免依赖错乱:
# 1. 启动抓包 mitmweb --listen-host 0.0.0.0 --listen-port 8080 # 2. 启动模拟器(Android Studio 里点启动,或命令行) emulator -avd Pixel_6_API_34 # 3. 确认 ADB 在线 adb devices # 4. 确认端口转发 adb forward tcp:8000 tcp:8000模拟器里确认代理已指向10.0.2.2:8080,证书已安装。
4.2 触发一次可抓包的请求
在模拟器里打开浏览器访问一个 HTTP 接口,或者用脚本触发:
adb shell am start -a android.intent.action.VIEW -d "https://httpbin.org/get"回到 mitmproxy 的 Web 界面http://127.0.0.1:8081,应该能看到这条请求。如果看不到,先检查模拟器代理是否生效——在模拟器浏览器访问http://mitm.it,能打开说明代理通了。
4.3 脚本回放并调用统一接口
运行上一节的 Python 脚本:
python auto_debug.py预期输出类似:
tap result: model result: {'choices': [{'message': {'content': '当前页面可能是...'}}]}tap result为空是正常的,input tap成功时没有输出。model result里能看到choices字段,说明统一接口调用成功。
4.4 抓包与回放的交叉验证
关键一步:在 mitmproxy 里找到脚本调用接口的那条请求,确认它的目标地址是taotoken.net,请求头里带了Authorization: Bearer sk-...。同时确认模拟器里 App 发出的请求也出现在 mitmproxy 里。两条流量都在同一个抓包会话里,说明抓包和自动化共用了一条网络路径。
如果脚本调用接口的请求没出现在 mitmproxy 里,说明脚本没走代理。Python 的 requests 默认读环境变量HTTP_PROXY/HTTPS_PROXY,可以在脚本里显式指定:
proxies = { "http": f"http://{cfg['proxy']['host']}:{cfg['proxy']['port']}", "https": f"http://{cfg['proxy']['host']}:{cfg['proxy']['port']}" } resp = requests.post(url, headers=headers, json=payload, proxies=proxies, timeout=60)注意这里的代理地址是主机视角的10.0.2.2还是127.0.0.1,取决于脚本跑在哪里。脚本跑在主机上就用127.0.0.1:8080,跑在模拟器里才用10.0.2.2:8080。这个细节搞反了,请求就会静默失败。
验证通过后,整条链路就是可复现的:换台机器,改 settings.json 里的 Key 和设备号,重跑一遍即可。
5. 抓包与自动化集成中的常见报错排查
这一节按真实报错来,每个都给出定位思路。
5.1 401 Unauthorized
最常见。先确认 Key 有没有带Bearer前缀,很多人直接填 Key 忘了前缀:
Authorization: Bearer sk-你的Key再确认 Key 有没有多余空格或换行。从控制台复制时容易带上尾部空格。最后确认 Base URL 拼对了,https://taotoken.net/api后面接的路径要和文档一致,路径错了也可能返回 401 而不是 404。
5.2 local proxy failed / connection refused
脚本报local proxy failed或Connection refused,说明代理地址或端口不对。检查三点:mitmproxy 是否在跑、监听地址是不是0.0.0.0、脚本里的代理地址是主机视角还是模拟器视角。前面说过,脚本在主机跑用127.0.0.1,在模拟器跑用10.0.2.2。
5.3 reading choices 相关报错
解析响应时报reading 'choices'或KeyError: 'choices',说明返回结构不是预期的 OpenAI 兼容格式。先打印完整响应看看:
print(json.dumps(resp.json(), ensure_ascii=False, indent=2))常见原因是模型 ID 填错,接口返回了错误信息而不是正常结果。确认model_id和文档里的一致。
5.4 OAuth / 认证方式不匹配
如果报 OAuth 相关错误,说明当前接入方式用的是 OAuth 而不是 API Key。检查请求头是不是误带了 OAuth token,或者配置里混入了其他认证方式。统一 Key 接入只用Authorization: Bearer,不要叠加其他认证头。
5.5 ADB 设备 offline 或 unauthorized
adb devices显示offline,等模拟器完全启动再试。显示unauthorized,在模拟器里确认 USB 调试授权弹窗,或者:
adb kill-server adb start-server adb devices重启 ADB 服务通常能解决。如果还不行,检查模拟器的开发者选项里 USB 调试是否打开。
5.6 证书安装后仍抓不到 HTTPS
Android 7.0+ 用户证书不被系统信任,前面讲过。如果目标 App 做了证书固定,用户证书和系统证书都不够,需要在 App 层面处理。调试阶段建议先用浏览器或普通 App 验证抓包链路,再针对特定 App 处理证书固定。
排查时记住一个原则:先确认网络层通不通(ping、curl),再确认代理层通不通(mitmproxy 有没有流量),最后确认应用层通不通(接口返回正不正常)。逐层排除,比一上来就改代码高效得多。
6. 把调试链路交接出去:统一 Key 与配置文件的落地建议
链路跑通只是第一步,能交接才算真正落地。交接的核心是让接手的人不需要问你就知道改哪里。
第一,settings.json 里不要提交真实 Key。用占位符,配一份settings.example.json,接手的人复制改名后填自己的 Key。Key 从 https://taotoken.net/api-keys 创建,接入细节看 https://taotoken.net/doc 。
第二,把启动顺序写成脚本。前面那串启动命令放进start_debug.sh,接手的人一条命令拉起环境:
#!/bin/bash set -e mitmweb --listen-host 0.0.0.0 --listen-port 8080 & emulator -avd Pixel_6_API_34 & adb wait-for-device adb forward tcp:8000 tcp:8000 echo "环境就绪,设备:$(adb devices | tail -n +2)"第三,验证动作固化成测试。把第 4 节的抓包到回放流程写成一个可重复执行的脚本,每次交接前跑一遍,输出「抓包可见 / 脚本可操作 / 接口可调用」三个结论。这样接手的人拿到的不只是一堆配置,而是一条能自证的链路。
长期做编码和 Agent 自动化的团队,可以考虑用 Coding Plan 把模型调用额度集中管理,地址是 https://taotoken.net/coding-plan 。需要快速验证模型返回时,模型对话入口在 https://taotoken.net/chat 。Claude Code 相关的接入配置参考 https://taotoken.net/claude-code 。
最后说个实际经验:调试链路里最容易被忽略的是「时间」。抓包会话、脚本执行、接口调用三者时间戳对不上时,排查会非常痛苦。建议在脚本里给每个动作打上时间戳,和 mitmproxy 的请求时间对照,能快速定位是哪一环慢了或断了。链路可复现的本质,是每一步都有可观测的痕迹。