emulator: Run a Headless Android Emulator in CI
The emulator binary launches an AVD; passing -no-window -no-audio -no-boot-anim runs it headless with less overhead on a CI runner.
A CI runner has no display and no sound card, so the emulator must run headless. Getting hardware acceleration and cold-boot flags right is the difference between a 60-second boot and a job that never gets past the boot animation.
What it does
emulator -avd <name> starts the named AVD in the qemu-based Android emulator. Headless flags drop the UI window, audio, and boot animation. Acceleration flags select KVM (Linux) or HAXM/HVF; snapshot flags control whether it cold-boots or restores saved state.
Common usage
$ANDROID_HOME/emulator/emulator -avd test_avd \
-no-window -no-audio -no-boot-anim \
-no-snapshot -gpu swiftshader_indirect \
-accel on -camera-back none &
adb wait-for-deviceOptions
| Flag | What it does |
|---|---|
| -avd <name> | AVD to launch |
| -no-window | Run without the emulator GUI window |
| -no-audio | Disable audio (no sound device on runners) |
| -no-boot-anim | Skip the boot animation to speed boot |
| -no-snapshot / -no-snapshot-load | Cold boot instead of restoring a snapshot |
| -gpu swiftshader_indirect | Software GL rendering when no GPU is present |
| -accel on | Require hardware acceleration (KVM/HAXM/HVF) |
In CI
On Linux runners you need KVM; on GitHub-hosted ubuntu-latest, KVM is available and the reactivecircus/android-emulator-runner action wires all of this up. Use -gpu swiftshader_indirect for reliable software rendering, and -no-snapshot to avoid restoring a corrupt snapshot from a prior run.
Common errors in CI
"/dev/kvm device permission denied" or "KVM is required to run this AVD" means the runner lacks KVM access; add the runner user to the kvm group or use a runner with nested virtualization. "emulator: ERROR: x86_64 emulation currently requires hardware acceleration!" means -accel/KVM is off. "Cannot find AVD system path" means the AVD or its system image is missing. A hang at the boot animation usually means a GPU flag mismatch; switch to -gpu swiftshader_indirect.