Docs › Android emulators on the workers
Android emulators on the workers
An Android emulator takes 4 to 10 GB of memory while it runs. Several of them at once (one per coding session, say) fill even a big Mac. sparehand emulator boots them on your other machines instead. They run headless there, and adb on this computer reaches each one at a serial of its own:
sparehand emulator setup myworker # once per machine: the SDK, the emulator, a system image
serial=$(sparehand emulator start Pixel_9) # boots it on a free worker, prints 127.0.0.1:15555
export ANDROID_SERIAL=$serial # adb, Gradle and test runners use it from here on
adb install app.apk && adb shell am start -n com.example/.MainActivity
adb exec-out screencap -p > screen.png
sparehand emulator stop $serial
start exits 255 when no worker can take the emulator, the same rule as sparehand run. Boot one on this computer then. A script or an agent does it like this:
if serial=$(sparehand emulator start Pixel_9); then export ANDROID_SERIAL=$serial
else emulator -avd Pixel_9 -no-window & fi # (exit 255: nothing free; boot it here)
Commands
| Command | What it does |
|---|---|
sparehand emulator setup WORKER [--api N] | Installs what is missing on that machine, in the user's home and without sudo: a JDK (Eclipse Temurin 21, only when there is no Java 17 or newer), the Android command-line tools, platform-tools, the emulator and a system image for API N (default: the newest stable one). The image is google_apis for the machine's own architecture: x86_64 on Linux and Windows (WSL), arm64-v8a on a Mac with Apple silicon. It also accepts the licences. It says what an administrator still has to do, if anything (see below). Run it again after that. |
sparehand emulator start [AVD] [--on WORKER] [--ram 3G] [--api N] | Boots an emulator and prints its serial (127.0.0.1:PORT) on stdout; progress goes to stderr. With the name of one of this computer's AVDs (~/.android/avd), the worker gets an AVD of the same name, Android version, image kind (google_apis, google_apis_ps16k…) and device, in its own architecture. The image is downloaded the first time it's needed. Without a name: sparehand_37 for --api 37, sparehand_latest otherwise. --ram: its memory, 1G to 16G (default 3G, whatever the AVD says). --on: only that worker. |
sparehand emulator test [AVD] [--on WORKER] [--ram 3G] [--api N] [-- COMMAND…] | An instrumented test run in one command: boots an emulator like start, runs COMMAND (default ./gradlew connectedDebugAndroidTest) on that same worker against it, brings the reports back and stops the emulator. See mobile builds. |
sparehand emulator list | The running emulators: serial, machine, AVD, memory, how long it has run, and who started it (the checkout it was started from and by what, e.g. my-app (Claude Code); SPAREHAND_EMULATOR_BY sets it). --tsv is for scripts. |
sparehand emulator stop SERIAL|all | Shuts it down on the worker (adb emu kill, then its process group), removes the forward and runs adb disconnect here. all stops every emulator you started. |
The dashboard shows the running emulators on each machine's card, with a Stop button for owners. The Activity tab lists every start, stop and setup.
Which machine gets it
Only machines that emulator setup has made ready are used, in this order:
- A machine at home before a cloud one. A Linux or Windows (WSL) machine before a Mac. Then the one with the most memory free.
- A machine must have room the way it would for a job: on, not paused, not strained, a job slot free,
SPAREHAND_MIN_MEMfree, CPU and GPU not busy. It also needs the emulator's memory plus 2 GB free. - A Mac takes at most
SPAREHAND_EMULATOR_MAC_MAXemulators (1) at a time. - A stopped cloud worker (
sparehand cloud add) is started for it only when nothing at home can take it, and never past its caps.
A running emulator counts as a job on its machine. It holds a job slot, its memory counts, and sparehand status lists it. A job that arrives later is the one stopped when the machine runs out of memory, not the emulator.
When it stops by itself
- Idle: after
SPAREHAND_EMULATOR_IDLE_MINminutes (30) with no adb traffic to it. The worker watches the bytes on the emulator's adb connections, so a test that is still running keeps it alive and a forgotten one goes. - Too long: after
SPAREHAND_EMULATOR_MAX_HOURShours (8) in all. - On a cloud worker, its limit is also what remains of the machine's hours for today (
--max-hours-day). When a cap is reached the hub stops the emulator, and the VM then stops after its idle time. While an emulator runs, the VM counts as busy, so its idle power-off doesn't end a test halfway.
An emulator that stopped by itself loses its forward and its adb connection here the next time Sparehand looks at that machine (status, list, the dashboard).
How it works
- On the worker:
~/.sparehand/androidholds the SDK, the JDK it may need and the AVDs (avd/);~/.sparehand/emulators/PORTdescribes a running emulator andPORT.logis its output. The emulator runs with-no-window -no-snapshot-save -no-boot-anim -no-audio -read-only, with-gpu swiftshader_indirecton Linux and-gpu hoston a Mac.-read-onlylets several sessions boot the same AVD at once, each from a clean state. - Its adb port (the console port + 1) is forwarded to this computer with
sparehand forward, at127.0.0.1:15555and up (SPAREHAND_EMULATOR_PORT). That stays clear of 5555 to 5585, which the adb server here scans by itself. Sparehand then runsadb connect, and the serial is ready whenstartreturns. - The emulator runs with
-skip-adb-auth, so adb here needs no key on the worker. The port only listens on the worker's own127.0.0.1, which the hub reaches through SSH. Anyone with a login on that worker could reach it too. - An app built only for arm64-v8a still runs on an x86_64 image: Android's own translation runs it. For performance measurements, or to rule that out, use a Mac worker (arm64).
sparehand remove NAME --cleanremoves all of it with the rest of~/.sparehand.
What an administrator may need to do
emulator setup says so, with the exact command:
- **Linux and Windows (WSL): the
kvmgroup.** The emulator needs/dev/kvm. When the user is not in its group, an administrator on the worker runssudo usermod -aG kvm USER; on Windows, in PowerShell there:wsl -u root usermod -aG kvm USER(setup prints the right one). The new group counts from the next login, whichemulator setupandstartalways use. On Windows, WSL needs nested virtualization (on by default in Windows 11). - A Linux server without a desktop: a few libraries. The emulator's own libraries expect some of the system's (NSS, PulseAudio, X11 bits) even headless. Setup names the packages:
sudo apt-get install -y libnss3 libpulse0 libxkbfile1 …. - A cloud VM needs nested virtualization: on Google Cloud an Intel machine (n2, c3; not AMD's c2d) with
--enable-nested-virtualization, and the build user in thekvmgroup.
Settings
| Setting | Default | Meaning |
|---|---|---|
SPAREHAND_EMULATOR_IDLE_MIN | 30 | Minutes without adb traffic after which an emulator stops (0: never). |
SPAREHAND_EMULATOR_MAX_HOURS | 8 | Hours an emulator may run in all (0: no limit; a cloud worker's daily cap still applies). |
SPAREHAND_EMULATOR_RAM | 3G | Its memory when --ram isn't given. |
SPAREHAND_EMULATOR_PORT | 15555 | The first local port for the serials. |
SPAREHAND_EMULATOR_MAC_MAX | 1 | Emulators at once on a Mac worker. |
SPAREHAND_EMULATOR_BOOT_WAIT | 420 | Seconds a worker waits for sys.boot_completed (set on the worker, in ~/.sparehand/env). |
For a coding agent, one line in its instructions does it: "Android emulators go through sparehand emulator start; use the printed serial as ANDROID_SERIAL; sparehand emulator stop when done; boot a local one only on exit 255."