呱呱聚合
← 返回基因库
自动化与脚本

在 Limrun 云 Android 模拟器上安装…

当 APK 已构建完成,需要在云端 Android 模拟器上安装、启动、交互、截图录像、查看日志或抓取网络流量时使用。

被复用0 次
复用有效—
置信度55%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 ella.he 的小呱沉淀
GDI C 38/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15

适用场景

在 Limrun 云 Android 模特定

解决思路

  1. 安装 CLI:`npm install --global lim`;认证用 `lim login` 或设置 `LIM_API_KEY`(它可能已在项目外设置,不要因为 .env 或 shell 里没有就索要)。CLI 是权威来源:若某个 flag 报错或需要未列出的 flag,先查 `lim android <subcommand> --help`,不要猜。
  2. 构建并安装:先用 `lim gradle build . --upload myapp.apk` 上传 APK 为命名资产,再 `lim android create --install-asset myapp.apk --no-open --no-connect`。create 会阻塞直到实例可驱动,无需等待启动。create 输出含签名 stream URL,用 Markdown 链接分享给用户,如 `[Live emulator](<signed-stream-url>)`;若有用户可见的浏览器就打开它并告知用户。create 也打印 console URL,但它需要 console 登录,分享时优先用签名 stream URL。
  3. create 常用 flag:`--reuse-if-exists`(复用同 label 的运行中实例)、`--rm`(CLI 退出时删除)、`--jurisdiction us|eu|as`(实例运行地区;不要用已废弃的 `--region`)、`--inactivity-timeout` / `--hard-timeout`、`--display-name`、`--label k=v`(之后用 `lim android list --label-selector k=v` 查找)。
  4. 从本地安装:先 `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 在发送完即返回,安装会在几秒内后台完成。新装 App 落在应用抽屉而非主屏,不要找图标;直接用 `lim android launch-app <package> --detach` 启动(不加 --detach 会阻塞直到 App 退出),或用 `lim android adb-shell -- sh -c "pm list packages | grep <name>"` 确认。
  5. 每次需要安装新版本 APK 时,用 sync 而不是重装:`lim android sync ./app-debug.apk`。它只发送与实例上已有 APK 的增量,然后重装。`--watch` 在文件变化时持续重新同步,`--launch-mode ForegroundIfRunning|RelaunchIfRunning` 控制每次安装后运行中 App 的行为。
  6. 定位正确实例:多数 `lim android` 命令默认用最后创建的实例,并根据 cwd 的 git 仓库/worktree 解析“当前”实例。在不同目录(或不在任何 git 仓库内)时,命令可能报告找不到最近实例,即使实例在运行。可靠做法:`lim android list` 查看所有实例及其 ID,然后对每个 `lim android` 命令都传 `--id <that-id>`。拿到 ID(格式 `android_<region>_<ulid>`)后,本会话所有 `lim android` 调用都传 `--id <android-instance-id>`。或者 `git init` 项目让 workspace 自行解析。控制多个实例时始终传 `--id`。
  7. 启动 App:`lim android launch-app com.example.app --detach`(启动并返回)、`lim android launch-app com.example.app`(启动并监视直到退出)、`lim android launch-app com.example.app --mode RelaunchIfRunning`(重启以获得干净状态)、`lim android terminate-app com.example.app`(停止以重置 App 状态)。不加 --detach 时 launch-app 会阻塞监视 App:崩溃、ANR 或被停止时,命令打印退出原因、含堆栈的崩溃详情和最近的 App 日志尾部,然后返回。这是不用 adb 查看 App 为何死亡的方式;运行中日志用 `lim android app-log`。没有 list-apps;用 `lim android adb-shell -- pm list packages` 发现包名,或从构建取 application ID。
  8. App 日志(无需 tunnel):`lim android app-log com.example.app --tail 100`(最近行,App 须在运行)、`lim android app-log com.example.app --follow`(实时流直到 Ctrl+C;不要把流导入上下文)。
  9. Shell 与文件(也无需 tunnel):`lim android adb-shell -- pm list packages -3`(类似 adb shell;参数放在 -- 之后)、`lim android adb-shell -- sh -c "dumpsys battery | grep level"`(管道需要显式 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 以命令的退出码退出。
  10. 完整 logcat 和交互式 adb 走 CLI tunnel:在后台 shell 启动并保持存活 `lim android connect`(打印 "Tunnel started on 127.0.0.1:<port>.")。connect 会替你运行 adb connect(adb 不在 PATH 时用 --adb-path)。打印的 `127.0.0.1:<port>` 是设备 serial,每次 adb 调用都用 `-s` 传入(`adb devices` 也会列出)。例如 `SERIAL=127.0.0.1:<port>` 后 `adb -s $SERIAL logcat -d | tail -100`(转储最近全设备日志,不要把流导入上下文)。tunnel 随启动它的进程生死:该 shell 退出后 `adb devices` 显示 serial 为 offline,而实例继续运行。重新运行 `lim android connect` 获得新 tunnel(端口每次都变)。陈旧的 offline serial 无害;`adb disconnect` 可清除。
  11. 测试改动:当模拟器交互是任务一部分时,每次安装或 sync 后用交互命令测试新增或改动功能。聚焦改动点,并对核心流程做快速冒烟测试。先读 element tree 看屏幕内容再操作:`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"`。
  12. 交互 App:优先按 resource id 点击,其次按可见文本或 content description,最后才用坐标:`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`;组合它们缩小匹配。浏览器网页里 resource id 是页面自己的 DOM id(如 searchIcon)且常为空;那里用 `--text` 加 `--class-name` 选择,或回退到节点 `bounds` 的坐标。想只查看匹配而不点击,用相同选择器的 `find-element`:`lim android find-element --text "Save"`(带 bounds 的匹配表)。
  13. 文本输入:直接定位字段,无需先点击聚焦。`type` 接受与 tap-element 相同的选择器(`--class-name android.widget.EditText --focused` 适用于无 resource id 的字段):`lim android type "hello world" --resource-id com.example.app:id/searchBox`、`lim android type "hello" --x 360 --y 400`(按坐标)、`lim android press-key enter`、`lim android press-key backspace`(`--modifier shift/control/alt/command` 组合)。
  14. 滚动与导航:`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"`(在默认浏览器打开;也会触发 deep link)。每次交互后重新运行 `element-tree` 确认 UI 已切换。点击与 element-tree 之间无需 sleep;点击会阻塞直到完成。open-url 后页面加载是异步的:反复运行 `element-tree` 直到期望节点出现。存在但无子节点的 `android.webkit.WebView` 表示页面仍在加载,不是树坏了。
  15. element tree 为空时:某些 React Native 和 Expo App 完全不暴露 accessibility 节点,使 element-tree、tap-element、find-element 失明(系统对话框仍暴露节点)。回退到按像素驱动:截图,读取目标坐标,用 `tap x y` / `type --x --y`。截图像素与点击坐标 1:1 映射,所以图中居中于 (360, 1322) 的按钮用 `lim android tap 360 1322` 点击。每次操作后重新截图确认结果。
  16. 截图与录像:screenshot 接受位置参数路径(不是 -o):`lim android screenshot screenshot.png`、`lim android screenshot screenshot.png --id <android-instance-id>`。功能断言(元素存在、文本、状态变化)用 element tree,截图只用于视觉属性。涉及运动(动画、游戏、流式 UI)时优先录像:`lim android record start`(非阻塞)、`lim android record stop -o /tmp/recording.mp4`。record stop 接受 `--quality 5-10`。录像帧是截图分辨率的一半,所以点击坐标从截图读取,绝不要从录像帧读。UI 改动时在 PR 里附演示视频让用户看到。
  17. 用音频文件模拟麦克风:对语音驱动流程(助手、语音转文字、音频通话),把本地音频文件作为模拟器麦克风播放。App 通过正常采集管线听到音频:`lim android play-on-microphone ./fixtures/command.wav`(默认循环)、`lim android play-on-microphone ./fixtures/command.mp3 --once`。支持 WAV 和 MP3。文件经 adb 推送,所以此命令需要本地 adb 二进制(不在 PATH 时用 --adb-path)并会打开自己的短命 tunnel。摄像头注入仅 iOS 支持;摄像头驱动测试流程用 limrun-ios-simulator。
  18. 塑造网络带宽:通过限制实例 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 清除限制)。
  19. 把 App 流量 tunnel 到你的机器:当 App 必须访问只有你机器能访问的服务(本地 dev server、仅 VPN 可达的 staging API),或需要查看其 HTTP 流量时,启动 destination tunnel。只有你选择的目的地会经运行 lim 的机器改道;其他都直接从实例出去。`lim android tunnel --selector localhost:8080 --detach --id <android-instance-id>`。精确选择器(`localhost:port` 或 `IP:port`,端口 >= 1024)成为模拟器上的 listener,也可通过 `10.0.2.2:<port>` 访问;App 到它的连接落到你机器并在那里拨号。域名选择器(`api.example.com`、`"*.corp.example"`)在模拟器上被拦截并从你机器拨号,所以你的 DNS 和 VPN 生效。自己通过 HTTPS 解析 DNS 的 App 会绕过域名拦截。在启动 App 之前启动 tunnel:更早打开的连接保持原路由。每实例一个 tunnel;第二次启动会失败。作为 agent 始终传 `--detach`:它在 tunnel READY 后返回并在后台进程保持存活。用 `lim android tunnel status --id <android-instance-id>`(状态、每选择器绑定、最后拨号失败)和 `lim android tunnel stop --id <android-instance-id>` 管理。
  20. 检查 HTTP 流量、抓 HAR、持久化网络日志:检查默认开启:经 tunnel 的每个 HTTP 和 HTTPS 请求都被解码,每请求打印一行摘要(detach 时在 tunnel 日志文件里),并在 console 网络面板实时显示。`lim android tunnel --selector "*.api.example" --har ./traffic.har --detach`(写含 body 的 HAR 1.2)、`lim android tunnel --selector "*.api.example" --persist --detach`(网络日志在实例终止后仍保留)。--persist 在 tunnel 停止或实例终止时上传含 body 的网络日志作为 session artifact;它出现在 console 的实例 session 页,可下载 HAR(默认存活 3 天,`--ttl <seconds>` 最多 30 天)。HTTPS 用模拟器信任的 CA 解码,所以有证书固定的 App 经被检查的域名选择器会失败:把固定主机排除在选择器外,或传 `--no-inspect` 以不透明方式中继字节(无摘要、HAR 或持久化)。
  21. 给人类用的预览 URL:把 APK 上传到 Limrun Asset Storage 并返回预览 URL,让用户在浏览器手动打开测试 App:`export ASSET_NAME=myapp.apk`(可为任意名)、`lim asset push ./app-debug.apk -n ${ASSET_NAME}`、`echo "https://console.limrun.com/preview?asset=${ASSET_NAME}&platform=android"`。在 Limrun console 打开该链接会预装 APK 配置一台模拟器。
  22. 清理:工作完成后可删除模拟器。delete 接受位置参数 ID(与其他命令不同,这里 `--id` 不是有效 flag):`lim android delete <android-instance-id>`。
  23. Gotchas:只发一种 ABI,不要混。模拟器是 x86_64 主机,报告 `x86_64,arm64-v8a` 并通过翻译运行 arm64 原生库。只有 `arm64-v8a` 库的 APK 能安装运行;x86_64 库原生运行且最快。混合 ABI 的 APK(部分 vendor SDK 库仅 arm64,其余 x86_64)会以 x86_64 安装,然后在仅 arm64 代码首次加载时以 `UnsatisfiedLinkError` 崩溃。仅 `armeabi-v7a` 的 APK 被拒,报 `INSTALL_FAILED_NO_MATCHING_ABIS`,且执行自带 ARM 命令行二进制的 App 在翻译下不受支持。选择器精确匹配:`tap-element --text` 和 `find-element --text` 需要 element-tree 中完整精确的字符串;子串匹配不到。install-app 在安装完成前返回:App 几秒后才落地;启动前用 find-element 或 pm list packages 确认。优先 `install-app <URL>` 或 `create --install-asset` 而非裸 `adb install`:大 APK 经 adb install 流式传输且零进度输出,看起来卡住数分钟,中途 kill 会损坏安装。ADB tunnel 是会话绑定的:它随启动它的 shell 死亡,而实例继续运行;用 `lim android connect` 重连并重读端口,每次都变。失败的 create 管道仍可能泄漏实例:若 create 调用客户端报错(broken pipe、JSON parse),检查 `lim android list`;实例可能已存在,应删除。空 element tree 通常意味着 React Native App,不是实例坏了。element-tree 可能很大:用 grep 提取所需,不要把整棵树倒进上下文。非 git 目录里实例解析可能失败:拿不准时传 `--id`。构建错误是构建 skill 的职责:若 APK 构建失败,失败在上游,回到 limrun-gradle。

适用边界 · 注意事项