安卓开发中遇到Medium Phone API 36.0 is already running as process XXX:把模拟器 endpoint 改到 TaoToken 的排查记录
1. 安卓开发中 Medium Phone API 36.0 报 already running 的真实场景你正在 Android Studio 里点绿色三角准备把刚写完的 Compose 页面丢进 Medium Phone API 36.0 跑一遍结果 Run 窗口弹出一行红字Medium Phone API 36.0 is already running as process 27416。点确定模拟器窗口没出来代码也没部署上去再点一次还是同一句只是进程号偶尔变一下。这个报错在安卓开发里非常典型本质不是模拟器坏了而是 AVD 的实例锁和 qemu 进程没被正确释放系统认为这个设备名已经被占用于是拒绝再拉起一个新的。Medium Phone API 36.0 是 Android Studio 较新版本里自带的一档中等尺寸手机镜像分辨率、内存、API 级别都卡在一个适合日常调试的区间所以很多人拿它当主力模拟器。也正因为用得频繁异常退出、强制关窗、ADB 抢端口这些情况叠加就会出现「进程还在、窗口没了」的僵尸态。此时 AVD Manager 里那个设备可能显示为 Running也可能显示为 Stopped但底层qemu-system-x86_64或qemu-system-aarch64进程确实还挂在后台占着 5554/5555 这组 ADB 端口。从进程占用和端口冲突两个角度切进去问题会清晰很多。进程占用指的是模拟器主进程没退干净端口冲突指的是 ADB server 与模拟器控制台端口对不上。你要做的不是反复点 Run而是先把残留进程和端口状态查清楚再用冷启动参数把实例干净地拉起来。等模拟器能稳定启动之后再把它的网络出口统一指向 TaoToken 的 API 通道这样后续无论是调模型接口还是做端侧 AI 功能联调Key 和 Base URL 都走同一套配置不用在每个项目里重复填。这篇记录按我实际排障的顺序写先定位进程和端口再给可复制的清理命令然后写冷启动参数和 endpoint 配置片段最后验证模拟器正常拉起、请求确实走了统一通道。中间会带上几个我踩过的坑比如taskkill杀了父进程但 qemu 子进程还在、adb kill-server之后端口没释放、以及 endpoint 配错导致模拟器内请求超时。你可以直接照着命令敲也可以只挑自己卡住的那一步看。需要先说明一点下面所有网络配置都指向合规的 API 服务地址不涉及任何网络加速工具纯粹是把模拟器的请求出口统一到一个可管理的 Key 通道上。安卓开发里做端侧模型调用、埋点上报、配置下发都会遇到「模拟器网络和真机网络不一致」的问题统一 endpoint 是常规做法。2. TaoToken 前置准备Key、Base URL 与模拟器网络出口在动手改模拟器 endpoint 之前先把 TaoToken 这边的三件套准备好API Key、Base URL、Model ID。这三样在后面的配置片段里会反复出现缺一个请求就会 401 或者 model not found。TaoToken 是一个面向开发者的模型 API 聚合通道你可以把它理解成一个统一的请求入口安卓模拟器、真机、后端服务都能用同一套 Key 去调不同模型省掉每个模型单独申请、单独配环境的麻烦。先拿 Key。打开官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注册登录后进控制台在 API Keys 页面创建一个新 Key。建议按项目命名比如android-medium-phone-debug这样后面在模拟器里看到请求记录时能一眼对上。Key 只在创建时完整显示一次复制后先存到密码管理器或者本地.env文件里别直接写进会提交到 Git 的代码。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数配置时不要自己加斜杠或者路径后缀。Model ID 根据你要调的能力选做对话类调试常用claude-sonnet-4-5这类标识具体以控制台模型列表为准。把这三个值记成一张小表配置项值用途Base URLhttps://taotoken.net/api所有请求的统一入口API Key控制台创建的 Key身份认证Model ID控制台模型列表里的标识指定调用的模型模拟器这边的网络出口有两种常见做法。一种是在模拟器内部装一个调试用的 HTTP 客户端 App把 Base URL 和 Key 填进去发请求另一种是在宿主机上跑一个本地代理脚本模拟器通过10.0.2.2这个特殊地址访问宿主机再由宿主机转发到 TaoToken。第二种更适合做端侧 AI 联调因为你可以集中管理 Key不用把 Key 硬编码进 APK。这里要提醒一个容易忽略的点Android 模拟器的10.0.2.2是宿主机回环地址的映射10.0.2.1是网关10.0.2.3是 DNS。如果你在模拟器里直接请求https://taotoken.net/api走的是模拟器自己的网络栈通常没问题但如果你想让请求经过宿主机的统一出口就要用10.0.2.2加宿主机端口。两种方式都合规选哪种取决于你的调试架构。Key 拿到后先别急着配模拟器用 curl 在宿主机上验一次确认 Key 和 Base URL 是通的。这一步能排掉大部分「配了半天发现 Key 复制错了」的情况。验证命令在下一节给先记住三件套的值后面配置片段直接替换。另外如果你后续要做长期编码或者 Agent 类调试可以了解下 Coding Plan它适合需要持续调用、批量任务的场景只是临时验证模型通不通用模型对话页面就够了。这些入口都在官网导航里按需取用。3. 可复制配置进程清理命令、冷启动参数与 endpoint 片段这一节是整篇的核心所有命令和配置都可以直接复制。先处理进程和端口再配模拟器启动参数最后写 endpoint 指向 TaoToken 的片段。顺序不要颠倒否则你会在一个还占着端口的实例上改配置改完发现根本没生效。3.1 定位残留进程与端口占用Windows 上用tasklist过滤 qemu 和 emulatortasklist | findstr /I qemu emulator你会看到类似qemu-system-x86_64.exe的进程PID 就是报错里那个 XXX。Linux 或 macOS 上用ps aux | grep -E qemu|emulator | grep -v grep端口这边查 5554 和 5555# Windows netstat -ano | findstr 5554 5555 # Linux / macOS lsof -i :5554 -i :5555如果 5554 被占用说明有一个模拟器控制台还活着5555 被占用则是 ADB 连接没断。两个端口都干净模拟器才能正常冷启动。3.2 清理命令确认不再需要当前实例后按 PID 杀taskkill /F /PID 27416Linux / macOSkill -9 27416如果杀完父进程发现 qemu 子进程还在用进程名批量清# Linux / macOS pkill -9 -f qemu-system # Windows按镜像名匹配 taskkill /F /IM qemu-system-x86_64.exe然后重置 ADBadb kill-server adb start-server adb devicesadb devices输出里不应该再有emulator-5554这种残留条目。如果有说明端口还没释放等几秒再查一次。3.3 冷启动参数不要直接点 Run用命令行冷启动参数写全emulator -avd Medium_Phone_API_36.0 -no-snapshot-load -no-snapshot-save -gpu swiftshader_indirect -no-boot-anim几个参数的作用-no-snapshot-load强制冷启动不读快照能绕开快照损坏导致的 already running-no-snapshot-save退出时不写快照避免下次启动又读到脏状态-gpu swiftshader_indirect用软件渲染兼容性最好显卡驱动导致的启动失败也能排掉-no-boot-anim跳过开机动画启动更快。AVD 名字里的空格在命令行里要处理Windows 上用引号包住Linux / macOS 上如果名字带空格同样加引号。名字可以在 AVD Manager 里看到或者用emulator -list-avds列出。3.4 endpoint 配置片段模拟器起来后配 endpoint。如果你用宿主机代理转发在宿主机上建一个taotoken-proxy.json{ baseUrl: https://taotoken.net/api, apiKey: sk-your-taotoken-key, model: claude-sonnet-4-5, timeoutMs: 30000, retry: 2 }模拟器内请求走http://10.0.2.2:8787/v1/chat/completions由宿主机代理转发到 TaoToken。如果你直接在模拟器内请求Android 项目的local.properties或BuildConfig里这样配// build.gradle.kts buildConfigField(String, TAOTOKEN_BASE_URL, \https://taotoken.net/api\) buildConfigField(String, TAOTOKEN_MODEL, \claude-sonnet-4-5\)Key 不要写进build.gradle.kts放local.propertiesTAOTOKEN_API_KEYsk-your-taotoken-key然后在build.gradle.kts里读取并注入BuildConfig。这样 Key 不会进版本库团队协作时每人用自己的 Key。如果你用的是 Claude Code 这类工具做辅助开发配置三件套的写法是 Base URL 填https://taotoken.net/apiKey 填控制台创建的 KeyModel ID 填模型列表里的标识。Cline MCP 或 Codex 的auth.json同理三个值缺一不可只填 Key 不填 Base URL 会走到默认地址报 401 或者连接超时。4. 验证请求模拟器正常拉起与统一通道检查配置写完必须验证不然你只是「觉得」配好了。验证分两层先确认模拟器本身正常拉起再确认请求确实走了 TaoToken 的统一通道。4.1 模拟器拉起检查冷启动命令执行后观察终端输出。正常情况会看到Boot completed之类的日志然后模拟器窗口出现。用adb devices应该看到emulator-5554 device。再用adb shell getprop sys.boot_completed返回1表示开机完成。如果一直返回空或者0说明还在启动或者卡住了回去看终端日志有没有qemu报错。4.2 宿主机 curl 验证在宿主机上先验 Key 和 Base URLcurl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-your-taotoken-key \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-5, messages: [{role: user, content: ping}] }返回里应该有choices字段和模型回复内容。如果返回 401检查 Key 有没有复制全、有没有多余空格如果返回 model not found检查 Model ID 是否和控制台一致。4.3 模拟器内请求验证在模拟器里跑你的调试 App或者用adb shell发一个请求adb shell curl -s -X POST http://10.0.2.2:8787/v1/chat/completions \ -H Content-Type: application/json \ -d {model:claude-sonnet-4-5,messages:[{role:user,content:ping}]}注意模拟器内不一定有 curl可以用toybox wget或者直接在 App 里打日志。更稳的做法是在 App 的网络层加一行日志把实际请求的 URL 打出来确认是https://taotoken.net/api或者宿主机代理地址而不是某个默认地址。4.4 统一通道检查去 TaoToken 控制台的请求记录页面看有没有刚才那几条请求。有记录说明模拟器的请求确实走了统一 Key 通道没有记录说明请求没到 TaoToken可能被模拟器网络拦了或者 endpoint 配错。这一步是很多人忽略的配完不查记录等线上出问题才发现 Key 根本没生效。验证通过后模拟器可以正常拉起请求也走统一通道整个排障闭环就完成了。后面再遇到 already running按第 3 节的顺序重来一遍即可。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth排障时遇到的报错不止 already running 一个下面这几个是我在配模拟器 endpoint 时实际撞到的按报错原文对照处理。5.1 401 Unauthorized最常见。原因通常是 Key 没带、Key 复制错、或者 Base URL 配成了不带/api的地址导致请求打到别的路径。检查顺序先看请求头有没有Authorization: Bearer sk-...再看 Key 前后有没有空格最后确认 Base URL 是https://taotoken.net/api。如果用的是 Claude Code 或 Cline检查三件套是否齐全只填 Key 不填 Base URL 会走默认地址一样 401。5.2 local proxy failed这个报错说明宿主机代理没起来或者模拟器请求的10.0.2.2:8787端口没监听。先在宿主机上确认代理进程在跑# Linux / macOS lsof -i :8787 # Windows netstat -ano | findstr 8787没有监听就重新启动代理脚本。另外检查模拟器内请求地址是不是写成了127.0.0.1模拟器里的127.0.0.1是模拟器自己不是宿主机必须用10.0.2.2。5.3 reading choices 相关报错这类报错通常出现在解析响应时比如error reading choices或者choices is null。原因一般是响应体不是预期的 JSON可能是请求被网关拦截返回了 HTML或者 Model ID 写错导致返回错误结构。先用 curl 在宿主机验一次确认返回结构正常再对比模拟器内请求的 URL 和 Header 是否一致。如果模拟器内返回的是 HTML检查是不是被模拟器的网络策略拦了或者 endpoint 写成了 http 而不是 https。5.4 OAuth 相关报错如果你用的是需要 OAuth 的工具链报错里出现OAuth字样通常是 token 过期或者回调地址不对。这类工具一般有自己的登录流程确认登录状态有效再检查配置里有没有把 OAuth token 和 API Key 搞混。TaoToken 的 API Key 是直接放在Authorization头里的不需要走 OAuth 流程两者不要混用。5.5 进程杀了又复活有时候taskkill之后进程号变了但又出现说明有守护进程或者 Android Studio 在自动重启模拟器。先关掉 Android Studio 里的模拟器自动启动选项再杀进程。另外检查有没有多个 AVD 实例共用同一个端口emulator -list-avds看看是不是有重名或者克隆的 AVD。5.6 冷启动后仍然报 already running这种情况多半是 AVD 目录下的锁文件没清。找到 AVD 目录通常在~/.android/avd/下对应设备名目录里有*.lock文件删掉再冷启动。Windows 上在C:\Users\你的用户名\.android\avd\。删锁文件前确认模拟器进程已经全部退出否则锁会被重新创建。6. 后续调试与统一通道的长期用法模拟器能稳定拉起、请求走统一通道之后日常调试会顺很多。我的习惯是把冷启动命令写成一个脚本放在项目根目录每次调试前跑一下避免手动敲参数漏掉-no-snapshot-load。脚本里顺便带上adb kill-server adb start-server把端口状态重置干净。Key 的管理上建议按环境分本地调试一个 KeyCI 一个 Key团队共享一个只读 Key。TaoToken 控制台可以创建多个 Key按名字区分请求记录里也能按 Key 过滤排查问题时能快速定位是哪个环境发的请求。模拟器里绝对不要硬编码 Key用local.properties加BuildConfig注入或者走宿主机代理Key 只存在宿主机上。如果你后面要做更重的端侧 AI 调试比如在模拟器里跑 Agent 循环、批量请求模型可以看下 Coding Plan它适合这种持续调用的场景。只是验证接口通不通用模型对话页面手动发几条就够了。接入文档里有各语言的示例Java、Kotlin、Python 都有照着改 Base URL 和 Key 就能用。最后留一个实用技巧模拟器启动慢的时候用emulator -avd Medium_Phone_API_36.0 -no-window无头启动配合adb做自动化测试省资源也省时间。需要看界面时再去掉-no-window。这个参数和冷启动参数可以叠加实测下来启动时间能少一截。