关于 limrun-android-emulator
limrun-android-emulator 是一项技能,可从 Linux、Windows、macOS、虚拟机或容器安装并控制 Limrun 云端 Android 模拟器上的应用。它涵盖应用交互、日志、文件传输、音频输入、网络限速和流量检查;构建由其他技能负责。
主要功能
- 01从文件、URL 或资源安装 APK
- 02启动、同步和控制已安装应用
- 03检查 UI 树、截图和录屏
- 04读取应用日志并传输文件
- 05模拟麦克风输入并限制 Wi-Fi 带宽
- 06隧道转发并检查应用网络流量
适用场景
- 01在云端模拟器上测试 Android 应用
- 02安装应用更新后检查 UI 行为
- 03通过日志和应用输出排查崩溃
- 04测试音频输入和受限网络条件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ limrun-inc/ skills --skill limrun-android-emulator安装后在 Claude Code 中输入/limrun-android-emulator确认已加载。
详细介绍
Limrun Android 模拟器
在任意环境(Linux、Windows、macOS、VM、容器)中,与 Limrun 云端 Android 模拟器上运行的应用交互。这项技能与构建方式无关:它假定 APK 已经构建完成,通常由 limrun-gradle 构建。构建相关内容请留在该技能中;此技能专注于操作正在运行的模拟器。
切勿使用本地模拟器、本地 Android SDK 或 Android Studio。
身份验证和 CLI
如有需要,请安装:npm install --global lim。身份验证方式为 lim login 或 LIM_API_KEY(即使 .env 和 shell 中没有显示,用户的环境中也可能已设置;索取密钥前请先检查)。CLI 是事实依据:本技能中的命令均已验证;如果某个标志报错,或者你需要使用此处未列出的标志,请查看 lim android <subcommand> --help,不要猜测。
安装应用
你可以使用 Limrun 的远程 Gradle 服务进行构建,并让 APK 自动安装到新建的模拟器上;也可以安装预先构建的本地 APK 或 URL。首先需要了解一个默认行为:除非另有说明,lim android create 会打开 ADB 隧道(--connect)和一个显示实时串流的浏览器标签页(--open)。作为 Agent,请始终传入 --no-open;除非你需要立即使用 adb,否则也请传入 --no-connect。
构建并安装
将 limrun-gradle 构建的 APK 作为命名资源上传,然后创建一个预先安装了该 APK 的模拟器:
lim gradle build . --upload myapp.apk
lim android create --install-asset myapp.apk --no-open --no-connect
create 会一直等待,直到实例准备就绪,可以开始操作;无需等待启动完成。create 的输出中包含签名串流 URL;请将其作为 Markdown 链接分享给用户,例如 [实时模拟器](<signed-stream-url>)。如果你有用户可见的浏览器,请在那里打开该 URL 并告知用户。create 还会输出控制台 URL:它会打开相同的实时视图,但需要登录控制台,因此分享时优先使用签名串流 URL。
签名链接可在无需登录的情况下打开共享设备工作区。完整实例令牌会显示 Inspect、Connect 以及日志、事件和网络检查器。仅串流令牌会显示屏幕和输入控件,但不包含这些工具。停止实例和选择应用需要通过经过身份验证的控制台进行。
实用的 create 标志:--reuse-if-exists(复用具有相同标签的运行中实例)、--rm(CLI 退出时删除)、--jurisdiction us|eu|as(指定实例运行的位置;不要使用已弃用的 --region)、--inactivity-timeout / --hard-timeout、--display-name 和 --label k=v(之后可使用 lim android list --label-selector k=v 查找带标签的实例)。
从本地安装应用
创建新的模拟器,然后安装本地文件或 URL:
lim android create --no-open --no-connect
lim android install-app ./app-debug.apk
lim android install-app https://example.com/app.apk
本地路径会先上传到 Limrun Asset Storage;URL 则由实例自行获取,对于大型 APK,这比从你的机器上传快得多。install-app 会在应用发送完毕后立即返回;安装会在后台于几秒内完成。新安装的应用会出现在应用抽屉中,而不是主屏幕上,因此不要查找应用图标;直接使用 lim android launch-app <package> --detach 启动应用(不加 --detach 时,命令会一直等待并监视应用,直到应用退出),或者使用 lim android adb-shell -- sh -c "pm list packages | grep <name>" 确认安装。
每次需要安装 APK 的新版本时,请同步而不是重新安装:
lim android sync ./app-debug.apk
它只会发送相对于实例上现有 APK 的增量内容,然后重新安装。--watch 会在文件更改时持续重新同步;--launch-mode ForegroundIfRunning|RelaunchIfRunning 则控制每次安装后正在运行的应用如何处理。
定位正确的实例
大多数 lim android 命令默认使用最近创建的实例,并根据当前工作目录所在的 Git 仓库 / worktree 解析“当前”实例。在其他目录(或任何 Git 仓库之外)运行命令时,即使有实例正在运行,命令也可能报告未找到最近的实例。可靠的做法是:
lim android list # shows all instances and their IDs
lim android element-tree --id <that-id> # pass --id to EVERY lim android command
获取 ID(格式为 android_<region>_<ulid>)后,在本次会话的其余时间里,向所有 lim android 调用传入 --id <android-instance-id>。也可以在项目中运行 git init,让工作区自行解析。控制多个实例时,务必传入 --id。
启动应用
按包名启动和停止已安装的应用:
lim android launch-app com.example.app --detach # launch and return
lim android launch-app com.example.app # launch and watch until it exits
lim android launch-app com.example.app --mode RelaunchIfRunning # restart for a clean state
lim android terminate-app com.example.app # stop it, e.g. to reset app state
不使用 --detach 时,launch-app 会阻塞并监视应用:应用崩溃、发生 ANR 或停止时,命令会打印退出原因、包含堆栈跟踪的崩溃详情以及最近的应用日志尾部,然后返回。无需使用 adb,就能通过这份报告了解应用退出的原因;应用运行期间要查看日志,请使用 lim android app-log(见下文)。没有 list-apps;请使用 lim android adb-shell -- pm list packages 查找包名,或从构建中获取应用 ID。
应用日志
查看单个应用的日志无需隧道:
lim android app-log com.example.app --tail 100 # recent lines (app must be running)
lim android app-log com.example.app --follow # stream live lines until Ctrl+C; don't stream into context
Shell 和文件
执行一次性 Shell 命令和传输文件也无需隧道:
lim android adb-shell -- pm list packages -3 # like adb shell; args go after --
lim android adb-shell -- sh -c "dumpsys battery | grep level" # pipes need an explicit shell
lim android push-file ./fixture.json /sdcard/Download/fixture.json
lim android pull-file /sdcard/Download/out.json ./out.json
这些操作使用与 adb shell 用户相同的权限运行,adb-shell 会以命令的退出代码退出。
通过隧道使用完整 logcat 和交互式 adb
完整设备 logcat 和所有交互式操作(Android Studio、scrcpy、流式传输)都通过 CLI 的隧道,使用原生 adb 进行。请在后台 Shell 中启动隧道,并保持其运行:
lim android connect # prints "Tunnel started on 127.0.0.1:<port>."
connect 会为你运行 adb connect(如果 adb 不在 PATH 中,请使用 --adb-path)。打印出的 127.0.0.1:<port> 是设备序列号;向每次 adb 调用传入 -s 和该序列号(adb devices 也会列出它):
SERIAL=127.0.0.1:<port>
adb -s $SERIAL logcat -d | tail -100 # dump recent full-device logs, don't stream into context
隧道的生命周期与启动它的进程相同:该 Shell 退出时,即使实例仍在运行,adb devices 也会将序列号显示为 offline。只需再次运行 lim android connect 即可获取新隧道(每次端口都会变化)。过期的离线序列号无妨;运行 adb disconnect 即可清除。
测试更改
如果任务涉及与模拟器交互,请在每次安装或同步后,使用交互命令测试新增或更改的功能。重点测试改动内容,并快速冒烟测试核心流程。开始操作前,先读取元素树,查看屏幕上显示的内容:
lim android element-tree
输出是单行原始 UIAutomator XML 层级结构,因此普通的 grep 会回显整个文档。请先将其拆分为每行一个节点,然后再 grep 所需的 text、resource-id、content-desc 或 bounds,而不是将整棵树都转储到上下文中:
lim android element-tree | sed 's/></>\n</g' | grep -i "save"
与应用交互
优先按资源 ID 点击,然后按可见文本或内容说明点击,最后才使用坐标:
lim android tap-element --resource-id com.example.app:id/startButton
lim android tap-element --text "Save"
lim android tap-element --content-desc "Open menu"
lim android tap 360 800
选择器值必须完全匹配,而不是子字符串匹配:--text "Save" 不会匹配名为 "Save draft" 的按钮。请从 element-tree 中原样复制值。若选择器没有匹配项,几秒内就会失败,并显示 No element found for selector。其他选择器包括:--class-name、--package-name、--index、--clickable、--enabled、--focused 和 --bounds-contains-x/y;可组合使用以缩小匹配范围。在浏览器中的网页上,资源 ID 是页面自身的 DOM ID(例如 searchIcon),通常为空;此时可同时使用 --text 和 --class-name 进行选择,或者退而使用节点 bounds 中的坐标。若要检查匹配项而不点击,请使用带有相同选择器的 find-element:
lim android find-element --text "Save" # table of matches with bounds
输入文本时,直接指定目标字段即可;无需先点击使其获得焦点。type 接受与 tap-element 相同的选择器(对于没有资源 ID 的字段,--class-name android.widget.EditText --focused 可用):
lim android type "hello world" --resource-id com.example.app:id/searchBox
lim android type "hello" --x 360 --y 400 # by coordinate
lim android press-key enter
lim android press-key backspace # --modifier shift/control/alt/command to combine
滚动和导航:
lim android scroll down --amount 600
lim android scroll up --amount 300
lim android press-key back
lim android press-key home
lim android open-url "https://example.com" # opens in the default browser; also fires deep links
每次交互后,都要重新运行 element-tree,确认 UI 已完成转换。点击后无需等待再运行 element-tree;点击命令会阻塞,直到操作完成。不过,open-url 后的页面加载是异步的:请重复运行 element-tree,直到预期节点出现。存在但没有子节点的 android.webkit.WebView 表示页面仍在加载,而不是树已损坏。
lim android element-tree
元素树为空时
某些 React Native 和 Expo 应用不会公开任何无障碍节点,因此 element-tree、tap-element 和 find-element 都无法识别元素(系统对话框仍会公开节点)。此时请改用像素操作:截取屏幕截图,读取目标的坐标,然后使用 tap x y / type --x --y。截图像素与点击坐标一一对应,因此,若图像中某个按钮的中心位于 (360, 1322),则使用 lim android tap 360 1322 点击。每次操作后都要重新截图以确认结果。
截图和视频
截图命令接受位置参数形式的路径(不是 -o):
lim android screenshot screenshot.png
lim android screenshot screenshot.png --id <android-instance-id>
功能断言(元素是否存在、文本、状态变化)请使用元素树,截图仅用于检查视觉属性。凡是涉及动态效果的内容(动画、游戏、流式 UI),优先使用视频:
lim android record start # non-blocking
lim android record stop -o /tmp/recording.mp4
record stop 接受 --quality 5-10。录制帧的分辨率是截图的一半,因此请从截图而不是视频帧中读取点击坐标。对于 UI 更改,请在拉取请求中附上演示视频,以便用户查看。
使用音频文件模拟麦克风
对于语音驱动流程(助手、语音转文本、音频通话),可将本地音频文件作为模拟器的麦克风输入。应用会通过正常的采集流程接收音频:
lim android play-on-microphone ./fixtures/command.wav # loops by default
lim android play-on-microphone ./fixtures/command.mp3 --once
WAV 和 MP3 均可使用。文件会通过 adb 推送,因此此命令需要本地的 adb 二进制文件(若它不在 PATH 中,请使用 --adb-path),并会自行开启一个短暂的隧道。摄像头注入仅适用于 iOS;对于由摄像头驱动的测试流程,请使用 limrun-ios-simulator。
限制网络带宽
通过限制实例的 Wi-Fi 带宽来测试慢速网络行为:
lim android set-wifi-bandwidth --down-kbps 1000 --up-kbps 500
lim android set-wifi-bandwidth --down-kbps 0 --up-kbps 0 # 0 clears the limit
通过你的机器隧道转发应用流量
当应用需要访问只有你的机器才能访问的服务(本地开发服务器、仅限 VPN 访问的预发布 API),或者你需要查看其 HTTP 流量时,请启动目标隧道。只有你选定的目标会通过运行 lim 的机器重新路由;其他所有流量都直接从实例发出。
lim android tunnel --selector localhost:8080 --detach --id <android-instance-id>
- 精确选择器(
localhost:port或IP:port,端口 >= 1024)会在模拟器上创建监听器,也可通过10.0.2.2:<port>访问;应用发往该地址的连接会到达你的机器,并在那里建立连接。 - 域名选择器(
api.example.com、"*.corp.example")会在模拟器上被拦截,并从你的机器发起连接,因此会使用你的 DNS 和 VPN。通过 HTTPS 自行解析 DNS 的应用会绕过域名拦截。 - 请在启动应用之前启动隧道:较早建立的连接会保留原有路由。每个实例只能有一个隧道;再次启动会失败。
作为 Agent,请始终传入 --detach:命令会在隧道就绪后返回,并在后台进程中保持隧道运行。使用以下命令管理隧道:
lim android tunnel status --id <android-instance-id> # state, per-selector binds, last dial failure
lim android tunnel stop --id <android-instance-id>
检查 HTTP 流量、捕获 HAR、保存网络日志
默认启用检查:隧道中的每个 HTTP 和 HTTPS 请求都会被解码,按每个请求一行摘要打印(分离运行时会写入隧道日志文件),并实时显示在控制台的网络面板中。
lim android tunnel --selector "*.api.example" --har ./traffic.har --detach # write HAR 1.2 with bodies
lim android tunnel --selector "*.api.example" --persist --detach # network log survives the instance
--persist 会在隧道停止或实例终止时,将包含请求正文的网络日志作为会话资源上传;该日志会显示在控制台中实例的会话页面,并提供 HAR 下载(默认保留 3 天,--ttl <seconds> 最长可设为 30 天)。HTTPS 使用模拟器信任的 CA 解码,因此启用检查的域名选择器会导致启用了证书固定的应用无法正常工作:请勿在选择器中包含固定证书的主机,或传入 --no-inspect,以不透明方式转发字节(不生成摘要、HAR 或持久化日志)。
通过专用隧道访问专用网络中的服务
组织可以运行专用隧道:由管理员通过 lim tunnel create 或在控制台(Network)中创建,并在组织网络内运行连接器;连接器通过 lim tunnel run 启动,可供所有指定该隧道的实例使用。当用户指定隧道时,请在创建实例时传入,而不要自行启动隧道:
lim android create --tunnel <tunnel-name>
应用可通过隧道选择器所指定的地址访问这些选择器;精确选择器也可通过 10.0.2.2:<port> 访问。选择器会在连接器所在的机器上解析,而不是在你的机器上解析。只有所有精确选择器都使用 1024 或更高的端口时,Android 实例才会接入。隧道会占用实例唯一的目标隧道,因此不要在该实例上启动或停止 lim android tunnel。
创建时会检查隧道;若隧道不可用,创建会失败:
tunnel <name> does not exist:名称错误或隧道尚未创建。请向用户询问正确的名称。tunnel <name> is offline:没有正在运行的连接器。请用户启动连接器。did not attach within 30s:连接器无法接入实例,或某个精确选择器使用了低于 1024 的端口。请用户检查连接器的输出,或将服务迁移到 1024 或更高的端口。
在这些情况下,循环重试创建都无济于事。
隧道令牌应保存在连接器所在的机器上。切勿让用户将令牌粘贴到对话中。
让所有设备流量都通过代理
隧道只承载已声明的目标;没有通配选择器。当用户希望所有流量都通过其代理发出(例如录制代理)时,请将模拟器的全局 HTTP 代理指向隧道可访问的代理。隧道检查会按原样转发发往代理的请求。对于 localhost 代理,请使用 8080 以外的端口;模拟器已经使用了 8080。
lim android tunnel --selector localhost:3128 --detach --id <android-instance-id>
lim android proxy set localhost:3128 --id <android-instance-id>
# later, before stopping the tunnel:
lim android proxy clear --id <android-instance-id>
如果使用选择器指向代理的专用隧道,只需执行 lim android proxy set。用户网络中的其他位置上的代理也同样适用:使用 --selector proxy.example.internal 和 lim android proxy set proxy.example.internal:3128。代理已设置但隧道未连接时,Chrome 的请求会失败,而不会直接发送。要在代理中读取 HTTPS 流量,请信任其 CA(见下文)。
信任 CA 证书
将 PEM CA 证书添加到模拟器的信任存储中,例如,为用户的测试服务器签名的专用 CA 根证书:
lim android ca add ./my-ca.pem --id <android-instance-id>
应用、WebView 和 Chrome 会在下次连接时信任该证书,在实例的整个生命周期内无需重启。每个文件只能包含一个 CA 证书;叶证书会被拒绝。仍使用证书固定的应用会拒绝该证书。要查看应用的 HTTP 和 HTTPS 流量,请改用隧道检查(见上文):无需使用用户自己的 CA。
面向用户的预览 URL
将 APK 上传到 Limrun Asset Storage,并返回预览 URL,供用户在浏览器中打开并手动测试应用:
export ASSET_NAME=myapp.apk # can be any name
lim asset push ./app-debug.apk -n ${ASSET_NAME}
echo "https://console.limrun.com/preview?asset=${ASSET_NAME}&platform=android"
在 Limrun 控制台中打开链接会配置模拟器,并在控制台安装 APK 的同时开始串流。安装期间会一直显示 Installing <asset name> 覆盖层。添加 openUrl,即可在启动后打开 URL 或应用深层链接,例如 &openUrl=myapp%3A%2F%2Fcheckout。使用 URLSearchParams.set("openUrl", url) 对完整 URL 及其查询字符串进行编码。应用安装、启动并打开 URL 时,串流会一直显示。预览就绪后,覆盖层会消失。
清理
工作完成后,可以删除模拟器。delete 接受位置参数 ID(与其他命令不同,这里 --id 不是有效标志):
lim android delete <android-instance-id>
注意事项
- 使用单一 ABI,不要混用。 模拟器使用 x86_64 宿主,并报告
x86_64,arm64-v8a,通过转译运行 arm64 原生库。仅包含arm64-v8a库的 APK 可以安装和运行;x86_64 库则以原生方式运行,速度最快。如果 APK 混用 ABI,例如部分供应商 SDK 库仅支持 arm64,其余库支持 x86_64,则 APK 会作为 x86_64 安装,并在首次加载仅支持 arm64 的代码时因UnsatisfiedLinkError崩溃。仅包含armeabi-v7a的 APK 会因INSTALL_FAILED_NO_MATCHING_ABIS而被拒绝;在转译环境下,不支持会执行自身捆绑的 ARM 命令行二进制文件的应用。 - 选择器必须完全匹配。
tap-element --text和find-element --text需要使用element-tree中完整且完全一致的字符串;子字符串无法匹配任何内容。 install-app会在安装完成前返回。 应用会在几秒后安装完成;启动前请使用find-element或pm list packages确认。优先使用install-app <URL>或create --install-asset,而不是直接使用adb install:通过adb install流式传输大型 APK 时不会显示进度,可能看起来像是卡住了好几分钟;在传输过程中强行终止会损坏安装。- ADB 隧道与会话绑定。 启动隧道的 shell 结束时,隧道也会关闭,但实例仍在运行;请使用
lim android connect重新连接并重新读取端口,端口每次都会变化。 - 创建管道失败仍可能遗留实例。 如果
create调用在客户端出错(管道断开、JSON 解析失败),请检查lim android list;实例可能仍然存在,应将其删除。 - **元素树为空通常表示这是一个 React Native 应用,**而不是实例损坏。请参阅上文“元素树为空时”。
element-tree的内容可能很大。 使用grep提取所需内容,而不是将整个树输出到上下文中。- 在非 git 目录中,实例解析可能失败。 请参阅上文“指定正确的实例”;如有疑问,请传入
--id。 - 构建错误应由构建技能处理。 如果 APK 无法构建,说明故障发生在上游;请返回 limrun-gradle。