limrun-android-emulator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLimrun Android Emulator
Limrun Android模拟器
Interact with an app running on a Limrun cloud Android emulator, from any
environment (Linux, Windows, macOS, VM, container). This skill is
build-agnostic: it assumes the APK was already built, usually by
limrun-gradle. Keep build concerns in that skill; this one is about
driving the running emulator.
Never use a local emulator, a local Android SDK, or Android Studio.
可从任意环境(Linux、Windows、macOS、虚拟机、容器)与运行在Limrun云Android模拟器上的应用进行交互。本技能与构建方式无关:它假设APK已完成构建,通常由limrun-gradle完成。构建相关事宜请使用该技能;本技能专注于驱动运行中的模拟器。
切勿使用本地模拟器、本地Android SDK或Android Studio。
Auth and CLI
身份验证与CLI
Install if needed: . Auth is or
(it may be set outside the project, so don't ask for it just
because it's missing from or the shell). The CLI is the source of truth:
the commands in this skill are verified, but if a flag errors or you need one
not shown here, check instead of guessing.
npm install --global limlim loginLIM_API_KEY.envlim android <subcommand> --help如需安装请执行:。身份验证可通过或设置(该密钥可能在项目外部设置,因此不要仅因为或shell中没有就向用户索要)。CLI是权威来源:本技能中的命令均已验证,但如果某个标志报错或你需要此处未展示的标志,请查看,不要自行猜测。
npm install --global limlim loginLIM_API_KEY.envlim android <subcommand> --helpInstalling an app
安装应用
You can build with Limrun's remote Gradle service and have the APK installed
on a fresh emulator automatically, or install a pre-built local APK or a URL.
One default to know first: opens an ADB tunnel
() and a browser tab with the live stream () unless told
otherwise. As an agent, pass always, and unless you
need adb right away.
lim android create--connect--open--no-open--no-connect你可以使用Limrun的远程Gradle服务构建应用,并将APK自动安装到全新的模拟器上,也可以安装预构建的本地APK或通过URL安装。首先要了解一个默认行为:会开启ADB隧道()和一个包含实时流的浏览器标签页(),除非明确指定不开启。作为Agent,请始终传递,除非你立即需要adb,否则传递。
lim android create--connect--open--no-open--no-connectBuild and install
构建并安装
Upload the APK built by limrun-gradle as a named asset, then create an
emulator with it pre-installed:
bash
lim gradle build . --upload myapp.apk
lim android create --install-asset myapp.apk --no-open --no-connectCreate blocks until the instance is ready to drive; no boot wait is needed.
The create output includes a signed stream URL; share it with the user as a
Markdown link, like Live emulator. If you have a
browser the user can see, open the URL there and tell them. Create also
prints a console URL: it opens the same live view but requires a console
login, so prefer the signed stream URL for sharing.
Useful create flags: (reuse a running instance with the
same labels), (delete when the CLI exits),
(where the instance runs; don't use , it is deprecated),
/ , , and
(find labeled instances later with ).
--reuse-if-exists--rm--jurisdiction us|eu|as--region--inactivity-timeout--hard-timeout--display-name--label k=vlim android list --label-selector k=v将limrun-gradle构建的APK作为命名资产上传,然后创建一个预安装该APK的模拟器:
bash
lim gradle build . --upload myapp.apk
lim android create --install-asset myapp.apk --no-open --no-connect创建实例直到其可被驱动;无需等待启动完成。创建输出包含一个签名流URL;请将其作为Markdown链接分享给用户,例如实时模拟器。如果你有用户可见的浏览器,可在此处打开该URL并告知用户。创建操作还会打印一个控制台URL:它会打开相同的实时视图,但需要控制台登录,因此优先选择签名流URL进行分享。
实用的创建标志:(复用具有相同标签的运行中实例)、(CLI退出时删除实例)、(实例运行的区域;请勿使用,该标志已废弃)、 / 、,以及(后续可通过查找带标签的实例)。
--reuse-if-exists--rm--jurisdiction us|eu|as--region--inactivity-timeout--hard-timeout--display-name--label k=vlim android list --label-selector k=vInstall app from local
从本地安装应用
Create a new emulator, then install a local file or a URL:
bash
lim android create --no-open --no-connect
lim android install-app ./app-debug.apk
lim android install-app https://example.com/app.apkA local path is uploaded to Limrun Asset Storage first; a URL is fetched by
the instance itself, which is much faster for large APKs than uploading from
your machine. returns as soon as the app is sent; the install
finishes in the background within seconds. Newly installed apps land in the
app drawer, not the home screen, so don't look for their icon; just launch
the app with (without
it blocks watching the app until it exits), or confirm over adb with
.
install-applim android launch-app <package> --detach--detachpm list packages | grep <name>Every time you need to install a new version of the APK, sync it instead of
reinstalling:
bash
lim android sync ./app-debug.apkIt sends only a delta against the APK already on the instance, then
reinstalls. keeps re-syncing on file changes, and
controls what happens to
the running app after each install.
--watch--launch-mode ForegroundIfRunning|RelaunchIfRunning创建一个新模拟器,然后安装本地文件或通过URL安装:
bash
lim android create --no-open --no-connect
lim android install-app ./app-debug.apk
lim android install-app https://example.com/app.apk本地路径会先上传到Limrun资产存储;URL则由实例自行获取,对于大型APK来说,这比从你的机器上传快得多。在应用发送完成后立即返回;安装会在后台几秒内完成。新安装的应用会出现在应用抽屉中,而非主屏幕,因此不要寻找其图标;只需使用启动应用(不带的话,会阻塞直到应用退出),或通过adb执行确认安装。
install-applim android launch-app <package> --detach--detachpm list packages | grep <name>每次需要安装新版本的APK时,请使用同步而非重新安装:
bash
lim android sync ./app-debug.apk它只会发送与实例上已有的APK之间的差异,然后重新安装。会在文件变化时持续重新同步,控制每次安装后运行中应用的行为。
--watch--launch-mode ForegroundIfRunning|RelaunchIfRunningTargeting the right instance
定位正确的实例
Most commands default to the last created instance and resolve
the "current" one from the git repo / worktree of your cwd. In a different
directory (or outside any git repo) a command can report that no recent
instance was found even though one is running. The reliable recipe:
lim androidbash
lim android list # shows all instances and their IDs
lim android element-tree --id <that-id> # pass --id to EVERY lim android commandOnce you have the ID (format ), pass
to all calls for the rest of the
session. Alternatively, the project so the workspace resolves on
its own. When controlling multiple instances, always pass .
android_<region>_<ulid>--id <android-instance-id>lim androidgit init--id大多数命令默认使用最后创建的实例,并从当前工作目录的git仓库/工作树解析“当前”实例。在不同目录(或任何git仓库之外)执行命令时,即使有实例在运行,也可能报告未找到最近的实例。可靠的解决方法:
lim androidbash
lim android list # 显示所有实例及其ID
lim android element-tree --id <that-id> # 为每个lim android命令传递--id参数获取ID后(格式为),在本次会话的所有调用中传递。或者,对项目执行,以便工作区自动解析实例。当控制多个实例时,请始终传递。
android_<region>_<ulid>lim android--id <android-instance-id>git init--idLaunching the app
启动应用
Launch and stop installed apps by package name:
bash
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 stateWithout , blocks watching the app: when it crashes,
ANRs, or is stopped, the command prints the exit reason, crash details with
the stack trace, and a recent app log tail, then returns. That report is the
way to see why an app died without adb; logs while the app is running still
need adb (below). There is no ; discover package names over adb
with , or take the application ID from the build.
--detachlaunch-applist-appspm list packages通过包名启动和停止已安装的应用:
bash
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 # 停止应用,例如重置应用状态不带时,会阻塞并监控应用:当应用崩溃、出现ANR或被停止时,该命令会打印退出原因、包含堆栈跟踪的崩溃详情以及最近的应用日志尾部,然后返回。该报告是无需adb即可查看应用终止原因的方式;应用运行时的日志仍需使用adb(见下文)。没有命令;可通过adb执行发现包名,或从构建中获取应用ID。
--detachlaunch-applist-appspm list packagesLogs, files, and shell over adb
通过adb查看日志、传输文件和执行shell命令
Logcat, file transfer, and arbitrary shell go through plain over the
CLI's tunnel. Start the tunnel in a background shell and keep it alive:
adbbash
lim android connect # prints "Tunnel started on 127.0.0.1:<port>."connectadb connect--adb-path127.0.0.1:<port>-sadb devicesbash
SERIAL=127.0.0.1:<port>
adb -s $SERIAL logcat -d | tail -100 # dump recent logs, don't stream into context
adb -s $SERIAL logcat -d --pid=$(adb -s $SERIAL shell pidof -s com.example.app) | tail -50 # only the app's logs (app must be running)
adb -s $SERIAL shell pm list packages | grep example # package name discovery
adb -s $SERIAL push ./fixture.json /sdcard/Download/
adb -s $SERIAL pull /sdcard/Download/out.json ./The tunnel lives and dies with the process that started it: when that shell
exits, shows the serial as while the instance keeps
running. Just run again to get a new tunnel (the port
changes each time). Stale offline serials are harmless;
clears them.
adb devicesofflinelim android connectadb disconnectLogcat、文件传输和任意shell命令通过CLI隧道使用普通执行。在后台shell中启动隧道并保持其运行:
adbbash
lim android connect # 打印“Tunnel started on 127.0.0.1:<port>.”connectadb connect--adb-path127.0.0.1:<port>-sadb devicesbash
SERIAL=127.0.0.1:<port>
adb -s $SERIAL logcat -d | tail -100 # 导出最近的日志,不要流式传输到上下文
adb -s $SERIAL logcat -d --pid=$(adb -s $SERIAL shell pidof -s com.example.app) | tail -50 # 仅查看应用日志(应用必须处于运行状态)
adb -s $SERIAL shell pm list packages | grep example # 发现包名
adb -s $SERIAL push ./fixture.json /sdcard/Download/
adb -s $SERIAL pull /sdcard/Download/out.json ./隧道随启动它的进程一起终止:当该shell退出时,会显示该序列号为,但实例仍在运行。只需再次运行即可获取新隧道(端口每次都会变化)。失效的离线序列号无害;可清除它们。
adb devicesofflinelim android connectadb disconnectTesting changes
测试变更
When emulator interaction is part of the task, test new or changed
functionality with the interaction commands after each install or sync. Focus
on what changed, plus a quick smoke test of core flows. Start by reading the
element tree to see what's on screen before acting:
bash
lim android element-treeThe output is the raw UIAutomator XML hierarchy on a single line, so a plain
grep echoes the whole document. Split it into one node per line first, then
grep for the , , , or you need
rather than dumping the whole tree into context:
textresource-idcontent-descboundsbash
lim android element-tree | sed 's/></>\n</g' | grep -i "save"当模拟器交互是任务的一部分时,请在每次安装或同步后使用交互命令测试新增或变更的功能。重点关注变更内容,再快速测试核心流程。在操作前先读取元素树,查看屏幕上的内容:
bash
lim android element-tree输出是单行的原始UIAutomator XML层级结构,因此普通grep会返回整个文档。请先将其拆分为每行一个节点,然后针对你需要的、、或进行grep,而不是将整个树输出到上下文:
textresource-idcontent-descboundsbash
lim android element-tree | sed 's/></>\n</g' | grep -i "save"Interacting with the app
与应用交互
Prefer tapping by resource id, then by visible text or content description,
then coordinates as a last resort:
bash
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 800Selector values match exactly, not by substring: does not
match a "Save draft" button. Copy the value verbatim from . A
selector that matches nothing fails in a couple of seconds with
. Other selectors: ,
, , , , , and
; combine them to narrow a match. On web pages in the
browser, resource ids are the page's own DOM ids (like ) and are
often empty; select by plus there, or fall back to
coordinates from the node's . To inspect matches without tapping, use
with the same selectors:
--text "Save"element-treeNo element found for selector--class-name--package-name--index--clickable--enabled--focused--bounds-contains-x/ysearchIcon--text--class-nameboundsfind-elementbash
lim android find-element --text "Save" # table of matches with boundsFor text input, target the field directly; no prior focus tap is needed.
takes the same selectors as ( works for a field with no resource id):
typetap-element--class-name android.widget.EditText --focusedbash
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 combineFor scrolling and navigation:
bash
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 linksAfter every interaction, re-run to confirm the UI transitioned.
No sleep is needed between a tap and ; the tap blocks until
done. A page load after is asynchronous though: re-run
until the node you expect appears. A present but childless
means the page is still loading, not a broken tree.
element-treeelement-treeopen-urlelement-treeandroid.webkit.WebViewbash
lim android element-tree优先通过资源ID点击,其次是可见文本或内容描述,最后才是坐标:
bash
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选择器值需要完全匹配,而非子字符串匹配:不会匹配“Save draft”按钮。请从中复制确切的值。如果选择器未匹配到任何元素,几秒后会失败并提示。其他选择器包括:、、、、、和;可组合这些选择器缩小匹配范围。在浏览器中的网页上,资源ID是页面自身的DOM ID(如),且通常为空;请在此处使用加进行选择,或根据节点的回退到坐标。如需在不点击的情况下检查匹配结果,请使用带有相同选择器的:
--text "Save"element-treeNo element found for selector--class-name--package-name--index--clickable--enabled--focused--bounds-contains-x/ysearchIcon--text--class-nameboundsfind-elementbash
lim android find-element --text "Save" # 包含匹配项及其边界的表格对于文本输入,请直接定位输入框;无需先点击获取焦点。接受与相同的选择器(适用于没有资源ID的输入框):
typetap-element--class-name android.widget.EditText --focusedbash
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组合按键对于滚动和导航:
bash
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" # 在默认浏览器中打开;也支持打开深度链接每次交互后,请重新运行以确认UI已切换。点击和之间无需等待;点击会阻塞直到完成。但后的页面加载是异步的:请重新运行直到你期望的节点出现。存在但无子节点的表示页面仍在加载,而非层级结构损坏。
element-treeelement-treeopen-urlelement-treeandroid.webkit.WebViewbash
lim android element-treeWhen the element tree is empty
当元素树为空时
Some React Native and Expo apps expose no accessibility nodes at all, which
leaves , , and blind (system dialogs
still expose nodes). Fall back to driving by pixels: take a screenshot, read
the coordinates of the target, and use / . Screenshot
pixels map 1:1 to tap coordinates, so a button centered at (360, 1322) in the
image is tapped with . Re-screenshot after each
action to confirm the result.
element-treetap-elementfind-elementtap x ytype --x --ylim android tap 360 1322部分React Native和Expo应用完全不暴露可访问性节点,这会导致、和无法工作(系统对话框仍会暴露节点)。请回退到基于像素的驱动方式:截取屏幕截图,读取目标的坐标,然后使用 / 。截图像素与点击坐标1:1对应,因此图像中居中于(360, 1322)的按钮可通过点击。每次操作后重新截图以确认结果。
element-treetap-elementfind-elementtap x ytype --x --ylim android tap 360 1322Screenshots and video
截图与视频录制
Screenshot takes a positional path (not ):
-obash
lim android screenshot screenshot.png
lim android screenshot screenshot.png --id <android-instance-id>Use the element tree for functional assertions (element existence, text, state
changes) and screenshots only for visual properties. For anything involving
motion (animations, gameplay, streaming UI), prefer video:
bash
lim android record start # non-blocking
lim android record stop -o /tmp/recording.mp4record stop--quality 5-10截图命令接受位置路径(而非):
-obash
lim android screenshot screenshot.png
lim android screenshot screenshot.png --id <android-instance-id>使用元素树进行功能断言(元素存在性、文本、状态变化),仅使用截图检查视觉属性。对于涉及运动的内容(动画、游戏、流式UI),优先使用视频录制:
bash
lim android record start # 非阻塞
lim android record stop -o /tmp/recording.mp4record stop--quality 5-10Simulate the microphone with an audio file
使用音频文件模拟麦克风
For voice-driven flows (assistants, speech-to-text, audio calls), play a local
audio file as the emulator's microphone. The app hears the audio through its
normal capture pipeline:
bash
lim android play-on-microphone ./fixtures/command.wav # loops by default
lim android play-on-microphone ./fixtures/command.mp3 --onceWAV and MP3 work. The file is pushed over adb, so this command needs a local
binary ( if it's not on PATH) and opens its own short-lived
tunnel. Camera injection is iOS-only; for camera-driven test flows use
limrun-ios-simulator.
adb--adb-path对于语音驱动流程(助手、语音转文字、音频通话),可播放本地音频文件作为模拟器的麦克风输入。应用会通过其正常的捕获管道听到该音频:
bash
lim android play-on-microphone ./fixtures/command.wav # 默认循环播放
lim android play-on-microphone ./fixtures/command.mp3 --once支持WAV和MP3格式。文件会通过adb推送,因此该命令需要本地二进制文件(如果不在PATH中,请使用)并会打开自己的短生命周期隧道。摄像头注入仅支持iOS;如需摄像头驱动的测试流程,请使用limrun-ios-simulator。
adb--adb-pathShape network bandwidth
调整网络带宽
Test slow-network behavior by capping the instance's Wi-Fi bandwidth:
bash
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通过限制实例的Wi-Fi带宽测试慢网络行为:
bash
lim android set-wifi-bandwidth --down-kbps 1000 --up-kbps 500
lim android set-wifi-bandwidth --down-kbps 0 --up-kbps 0 # 0表示清除限制Preview URL for humans
面向用户的预览URL
Upload the APK to Limrun Asset Storage and return a preview URL for the user
to open and test the app manually in the browser:
bash
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"Opening the link in the Limrun console provisions an emulator with the APK
pre-installed.
将APK上传到Limrun资产存储,并返回一个预览URL,供用户在浏览器中打开并手动测试应用:
bash
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控制台中打开该链接会自动创建一个预安装该APK的模拟器。
Cleanup
清理
When the work is completed, you can delete the emulator. takes a
positional ID ( is not a valid flag here, unlike other commands):
delete--idbash
lim android delete <android-instance-id>工作完成后,你可以删除模拟器。命令接受位置参数ID(与其他命令不同,此处不是有效标志):
delete--idbash
lim android delete <android-instance-id>Gotchas
注意事项
- The fleet is x86_64. Emulators report ABIs and run arm64 code through translation, but an APK whose native libraries are arm64-only for some vendor SDKs installs fine and then crashes with
x86_64,arm64-v8awhen that code first loads. Build with x86_64 native libs included.UnsatisfiedLinkError - Selectors match exactly. and
tap-element --textneed the full, exact string fromfind-element --text; substrings match nothing.element-tree - returns before the install finishes. The app lands a few seconds later; verify with
install-apporfind-elementbefore launching. Preferpm list packagesorinstall-app <URL>over rawcreate --install-asset: a big APK overadb installstreams with zero progress output and looks hung for minutes, and killing it mid-stream corrupts the install.adb install - The ADB tunnel is session-bound. It dies with the shell that started it
while the instance keeps running; reconnect with and re-read the port, it changes every time.
lim android connect - Failed create pipes can still leak an instance. If a invocation errors client-side (broken pipe, JSON parse), check
create; the instance may exist anyway and should be deleted.lim android list - Empty element tree usually means a React Native app, not a broken instance. See "When the element tree is empty" above.
- can be large. Pipe through
element-treeto extract what you need rather than dumping the whole tree into context.grep - Instance resolution can miss in a non-git dir. See "Targeting the right
instance" above; pass when in doubt.
--id - Build errors are the build skill's job. If the APK isn't building, the failure is upstream; go back to limrun-gradle.
- 模拟器集群为x86_64架构。模拟器报告的ABI为,并通过转译运行arm64代码,但某些供应商SDK仅包含arm64原生库的APK安装后,在首次加载该代码时会因
x86_64,arm64-v8a崩溃。请构建包含x86_64原生库的APK。UnsatisfiedLinkError - 选择器需完全匹配。和
tap-element --text需要与find-element --text中的完整字符串完全匹配;子字符串无法匹配。element-tree - 在安装完成前返回。应用会在几秒后安装完成;请在启动前使用
install-app或find-element验证。优先使用pm list packages或install-app <URL>,而非直接使用create --install-asset:通过adb install传输大型APK时,没有进度输出,看起来像是卡住了几分钟,中途终止会导致安装损坏。adb install - ADB隧道与会话绑定。隧道随启动它的shell一起终止,但实例仍在运行;请重新运行并读取新端口,端口每次都会变化。
lim android connect - 创建失败的管道仍可能导致实例泄漏。如果调用在客户端报错(管道破裂、JSON解析错误),请检查
create;实例可能已存在,应予以删除。lim android list - 空元素树通常表示React Native应用,而非实例损坏。请查看上文“当元素树为空时”部分。
- 可能很大。请通过
element-tree提取所需内容,而非将整个树输出到上下文。grep - 在非git目录中可能无法解析实例。请查看上文“定位正确的实例”部分;如有疑问,请传递。
--id - 构建错误属于构建技能的范畴。如果APK构建失败,问题出在上游;请回到limrun-gradle技能。