limrun-android-emulator

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Limrun 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:
npm install --global lim
. Auth is
lim login
or
LIM_API_KEY
(it may be set outside the project, so don't ask for it just because it's missing from
.env
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
lim android <subcommand> --help
instead of guessing.
如需安装请执行:
npm install --global lim
。身份验证可通过
lim login
或设置
LIM_API_KEY
(该密钥可能在项目外部设置,因此不要仅因为
.env
或shell中没有就向用户索要)。CLI是权威来源:本技能中的命令均已验证,但如果某个标志报错或你需要此处未展示的标志,请查看
lim android <subcommand> --help
,不要自行猜测。

Installing 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:
lim android create
opens an ADB tunnel (
--connect
) and a browser tab with the live stream (
--open
) unless told otherwise. As an agent, pass
--no-open
always, and
--no-connect
unless you need adb right away.
你可以使用Limrun的远程Gradle服务构建应用,并将APK自动安装到全新的模拟器上,也可以安装预构建的本地APK或通过URL安装。首先要了解一个默认行为:
lim android create
会开启ADB隧道(
--connect
)和一个包含实时流的浏览器标签页(
--open
),除非明确指定不开启。作为Agent,请始终传递
--no-open
,除非你立即需要adb,否则传递
--no-connect

Build 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-connect
Create 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-if-exists
(reuse a running instance with the same labels),
--rm
(delete when the CLI exits),
--jurisdiction us|eu|as
(where the instance runs; don't use
--region
, it is deprecated),
--inactivity-timeout
/
--hard-timeout
,
--display-name
, and
--label k=v
(find labeled instances later with
lim 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进行分享。
实用的创建标志:
--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
查找带标签的实例)。

Install 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.apk
A 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.
install-app
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
lim android launch-app <package> --detach
(without
--detach
it blocks watching the app until it exits), or confirm over adb with
pm 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.apk
It sends only a delta against the APK already on the instance, then reinstalls.
--watch
keeps re-syncing on file changes, and
--launch-mode ForegroundIfRunning|RelaunchIfRunning
controls what happens to the running app after each install.
创建一个新模拟器,然后安装本地文件或通过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来说,这比从你的机器上传快得多。
install-app
在应用发送完成后立即返回;安装会在后台几秒内完成。新安装的应用会出现在应用抽屉中,而非主屏幕,因此不要寻找其图标;只需使用
lim android launch-app <package> --detach
启动应用(不带
--detach
的话,会阻塞直到应用退出),或通过adb执行
pm list packages | grep <name>
确认安装。
每次需要安装新版本的APK时,请使用同步而非重新安装:
bash
lim android sync ./app-debug.apk
它只会发送与实例上已有的APK之间的差异,然后重新安装。
--watch
会在文件变化时持续重新同步,
--launch-mode ForegroundIfRunning|RelaunchIfRunning
控制每次安装后运行中应用的行为。

Targeting the right instance

定位正确的实例

Most
lim android
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:
bash
lim android list                          # shows all instances and their IDs
lim android element-tree --id <that-id>   # pass --id to EVERY lim android command
Once you have the ID (format
android_<region>_<ulid>
), pass
--id <android-instance-id>
to all
lim android
calls for the rest of the session. Alternatively,
git init
the project so the workspace resolves on its own. When controlling multiple instances, always pass
--id
.
大多数
lim android
命令默认使用最后创建的实例,并从当前工作目录的git仓库/工作树解析“当前”实例。在不同目录(或任何git仓库之外)执行命令时,即使有实例在运行,也可能报告未找到最近的实例。可靠的解决方法:
bash
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
,以便工作区自动解析实例。当控制多个实例时,请始终传递
--id

Launching 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 state
Without
--detach
,
launch-app
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
list-apps
; discover package names over adb with
pm list packages
, or take the application ID from the build.
通过包名启动和停止已安装的应用:
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                        # 停止应用,例如重置应用状态
不带
--detach
时,
launch-app
会阻塞并监控应用:当应用崩溃、出现ANR或被停止时,该命令会打印退出原因、包含堆栈跟踪的崩溃详情以及最近的应用日志尾部,然后返回。该报告是无需adb即可查看应用终止原因的方式;应用运行时的日志仍需使用adb(见下文)。没有
list-apps
命令;可通过adb执行
pm list packages
发现包名,或从构建中获取应用ID。

Logs, files, and shell over adb

通过adb查看日志、传输文件和执行shell命令

Logcat, file transfer, and arbitrary shell go through plain
adb
over the CLI's tunnel. Start the tunnel in a background shell and keep it alive:
bash
lim android connect        # prints "Tunnel started on 127.0.0.1:<port>."
connect
runs
adb connect
for you (use
--adb-path
if adb isn't on PATH). The printed
127.0.0.1:<port>
is the device serial; pass it with
-s
to every adb call (
adb devices
also lists it):
bash
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,
adb devices
shows the serial as
offline
while the instance keeps running. Just run
lim android connect
again to get a new tunnel (the port changes each time). Stale offline serials are harmless;
adb disconnect
clears them.
Logcat、文件传输和任意shell命令通过CLI隧道使用普通
adb
执行。在后台shell中启动隧道并保持其运行:
bash
lim android connect        # 打印“Tunnel started on 127.0.0.1:<port>.”
connect
会为你执行
adb connect
(如果adb不在PATH中,请使用
--adb-path
)。打印的
127.0.0.1:<port>
是设备序列号;请在每个adb调用中使用
-s
传递该序列号(
adb devices
也会列出它):
bash
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 devices
会显示该序列号为
offline
,但实例仍在运行。只需再次运行
lim android connect
即可获取新隧道(端口每次都会变化)。失效的离线序列号无害;
adb disconnect
可清除它们。

Testing 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-tree
The 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
text
,
resource-id
,
content-desc
, or
bounds
you need rather than dumping the whole tree into context:
bash
lim android element-tree | sed 's/></>\n</g' | grep -i "save"
当模拟器交互是任务的一部分时,请在每次安装或同步后使用交互命令测试新增或变更的功能。重点关注变更内容,再快速测试核心流程。在操作前先读取元素树,查看屏幕上的内容:
bash
lim android element-tree
输出是单行的原始UIAutomator XML层级结构,因此普通grep会返回整个文档。请先将其拆分为每行一个节点,然后针对你需要的
text
resource-id
content-desc
bounds
进行grep,而不是将整个树输出到上下文:
bash
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 800
Selector values match exactly, not by substring:
--text "Save"
does not match a "Save draft" button. Copy the value verbatim from
element-tree
. A selector that matches nothing fails in a couple of seconds with
No element found for selector
. Other selectors:
--class-name
,
--package-name
,
--index
,
--clickable
,
--enabled
,
--focused
, and
--bounds-contains-x/y
; combine them to narrow a match. On web pages in the browser, resource ids are the page's own DOM ids (like
searchIcon
) and are often empty; select by
--text
plus
--class-name
there, or fall back to coordinates from the node's
bounds
. To inspect matches without tapping, use
find-element
with the same selectors:
bash
lim android find-element --text "Save"   # table of matches with bounds
For text input, target the field directly; no prior focus tap is needed.
type
takes the same selectors as
tap-element
(
--class-name android.widget.EditText --focused
works for a field with no resource id):
bash
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
For 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 links
After every interaction, re-run
element-tree
to confirm the UI transitioned. No sleep is needed between a tap and
element-tree
; the tap blocks until done. A page load after
open-url
is asynchronous though: re-run
element-tree
until the node you expect appears. A present but childless
android.webkit.WebView
means the page is still loading, not a broken tree.
bash
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
选择器值需要完全匹配,而非子字符串匹配:
--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
bash
lim android find-element --text "Save"   # 包含匹配项及其边界的表格
对于文本输入,请直接定位输入框;无需先点击获取焦点。
type
接受与
tap-element
相同的选择器(
--class-name android.widget.EditText --focused
适用于没有资源ID的输入框):
bash
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"   # 在默认浏览器中打开;也支持打开深度链接
每次交互后,请重新运行
element-tree
以确认UI已切换。点击和
element-tree
之间无需等待;点击会阻塞直到完成。但
open-url
后的页面加载是异步的:请重新运行
element-tree
直到你期望的节点出现。存在但无子节点的
android.webkit.WebView
表示页面仍在加载,而非层级结构损坏。
bash
lim android element-tree

When the element tree is empty

当元素树为空时

Some React Native and Expo apps expose no accessibility nodes at all, which leaves
element-tree
,
tap-element
, and
find-element
blind (system dialogs still expose nodes). Fall back to driving by pixels: take a screenshot, read the coordinates of the target, and use
tap x y
/
type --x --y
. Screenshot pixels map 1:1 to tap coordinates, so a button centered at (360, 1322) in the image is tapped with
lim android tap 360 1322
. Re-screenshot after each action to confirm the result.
部分React Native和Expo应用完全不暴露可访问性节点,这会导致
element-tree
tap-element
find-element
无法工作(系统对话框仍会暴露节点)。请回退到基于像素的驱动方式:截取屏幕截图,读取目标的坐标,然后使用
tap x y
/
type --x --y
。截图像素与点击坐标1:1对应,因此图像中居中于(360, 1322)的按钮可通过
lim android tap 360 1322
点击。每次操作后重新截图以确认结果。

Screenshots and video

截图与视频录制

Screenshot takes a positional path (not
-o
):
bash
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.mp4
record stop
accepts
--quality 5-10
. Recorded frames are half the screenshot resolution, so read tap coordinates from screenshots, never from video frames. For UI changes, include a demo video in the pull request so the user can see it.
截图命令接受位置路径(而非
-o
):
bash
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.mp4
record stop
接受
--quality 5-10
参数。录制的帧分辨率为截图的一半,因此请从截图读取点击坐标,切勿从视频帧读取。对于UI变更,请在拉取请求中包含演示视频,以便用户查看。

Simulate 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 --once
WAV and MP3 work. The file is pushed over adb, so this command needs a local
adb
binary (
--adb-path
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.
对于语音驱动流程(助手、语音转文字、音频通话),可播放本地音频文件作为模拟器的麦克风输入。应用会通过其正常的捕获管道听到该音频:
bash
lim android play-on-microphone ./fixtures/command.wav          # 默认循环播放
lim android play-on-microphone ./fixtures/command.mp3 --once
支持WAV和MP3格式。文件会通过adb推送,因此该命令需要本地
adb
二进制文件(如果不在PATH中,请使用
--adb-path
)并会打开自己的短生命周期隧道。摄像头注入仅支持iOS;如需摄像头驱动的测试流程,请使用limrun-ios-simulator

Shape 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.
delete
takes a positional ID (
--id
is not a valid flag here, unlike other commands):
bash
lim android delete <android-instance-id>
工作完成后,你可以删除模拟器。
delete
命令接受位置参数ID(与其他命令不同,此处
--id
不是有效标志):
bash
lim android delete <android-instance-id>

Gotchas

注意事项

  • The fleet is x86_64. Emulators report
    x86_64,arm64-v8a
    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
    UnsatisfiedLinkError
    when that code first loads. Build with x86_64 native libs included.
  • Selectors match exactly.
    tap-element --text
    and
    find-element --text
    need the full, exact string from
    element-tree
    ; substrings match nothing.
  • install-app
    returns before the install finishes.
    The app lands a few seconds later; verify with
    find-element
    or
    pm list packages
    before launching. Prefer
    install-app <URL>
    or
    create --install-asset
    over raw
    adb install
    : a big APK over
    adb install
    streams with zero progress output and looks hung for minutes, and killing it mid-stream corrupts the install.
  • The ADB tunnel is session-bound. It dies with the shell that started it while the instance keeps running; reconnect with
    lim android connect
    and re-read the port, it changes every time.
  • Failed create pipes can still leak an instance. If a
    create
    invocation errors client-side (broken pipe, JSON parse), check
    lim android list
    ; the instance may exist anyway and should be deleted.
  • Empty element tree usually means a React Native app, not a broken instance. See "When the element tree is empty" above.
  • element-tree
    can be large.
    Pipe through
    grep
    to extract what you need rather than dumping the whole tree into context.
  • Instance resolution can miss in a non-git dir. See "Targeting the right instance" above; pass
    --id
    when in doubt.
  • 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为
    x86_64,arm64-v8a
    ,并通过转译运行arm64代码,但某些供应商SDK仅包含arm64原生库的APK安装后,在首次加载该代码时会因
    UnsatisfiedLinkError
    崩溃。请构建包含x86_64原生库的APK。
  • 选择器需完全匹配
    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技能。