A lightweight Android emulator for Expo and React Native developers, built with Tauri v2, QEMU, and Android-x86.
Read before using: This project has fundamental performance limitations that make it unsuitable for daily development. See Limitations below.
Mobiler boots Android-x86 9.0 inside QEMU and wraps it in a small Tauri desktop app. The goal was to give React Native and Expo developers a lighter alternative to Android Studio's emulator, which consumes 4+ GB of RAM and takes minutes to boot.
The Tauri shell is ~5MB. Android boots in roughly 60 seconds. ADB connects automatically over TCP.
- Desktop shell: Tauri v2 (Rust + React 19 + TypeScript)
- Styling: Tailwind CSS 3
- State: Zustand v5
- Virtualization: QEMU with WHPX (Windows) / HVF (macOS) / KVM (Linux)
- Android: Android-x86 9.0-r2
- Display: VNC over WebSocket
- Device bridge: ADB over TCP
- QEMU installed and on PATH (or placed in
src-tauri/bin/) - Android Platform Tools (ADB)
- An Android-x86 9.0-r2 disk image (
.qcow2) — see setup below - Rust 1.80+ and Node.js 18+
- Windows: Hyper-V / Windows Hypervisor Platform enabled
1. Install dependencies
npm install2. Get QEMU and ADB binaries
Run the helper script, or place the binaries manually in src-tauri/bin/:
bash scripts/download-sidecars.sh3. Prepare the Android disk image
bash scripts/prepare-android-image.shThis downloads the Android-x86 9.0-r2 ISO (~900MB), creates a 16GB QCOW2 disk, and walks you through the installer. After install, extract the kernel and initrd next to the image file.
4. Run
npm run tauri devThis section exists so you know what you are getting into before spending time on it.
The single biggest problem is rendering. Mobiler uses software rendering: Android's compositor draws every frame using the CPU, which gets encoded by VNC and sent over a WebSocket to a browser canvas. The pipeline looks like this:
Android CPU rendering -> VGA framebuffer -> VNC encode -> WebSocket -> browser canvas
Every UI frame goes through at least four transformations. Scrolling feels slow. Google's Android emulator avoids this entirely by using GPU passthrough via ANGLE, which is only available in their custom QEMU fork, not in upstream QEMU.
Android-x86's init script only scans /dev/sd* paths for storage, which means the disk must use the emulated IDE interface instead of the faster virtio-blk. This slows down every app install, file read, and asset load.
The network card uses the emulated e1000 adapter instead of the faster paravirtualized virtio-net, for compatibility during early boot. Metro bundler transfers (JS bundles can be 10-50MB) are noticeably slower than on a physical device.
Without virgl or a native display backend, there is no OpenGL ES acceleration in apps. Maps, 3D UI, animations, and any app using hardware-accelerated rendering will either be very slow or fail entirely.
On Windows, QEMU uses WHPX (Windows Hypervisor Platform). WHPX is measurably slower than KVM (Linux) or HVF (macOS) for the same guest workload. It also requires kernel-irqchip=off, which degrades interrupt handling inside the VM.
Under WHPX, 32-bit processes crash due to a bug in how the hypervisor handles vDSO (the virtual system call interface). The fix is to boot Android in 64-bit-only mode by patching the zygote configuration before boot. This means any app or library that ships 32-bit native code will not run.
There is no snapshot support. Every launch boots Android from scratch, which takes approximately 60 seconds on decent hardware.
Android-x86 9.0-r2 targets API level 28 (Android 9). Apps that require API level 30+ (Android 11) or use newer APIs may crash. Google's official emulator ships Android 14 images — a five-year gap in SDK coverage.
Audio is disabled in the QEMU configuration. Apps that depend on audio output will fail silently.
The VM manager is a global singleton. Only one emulator instance can run at a time.
Zygote64 patch — On boot, Mobiler intercepts the Android init sequence via the serial console and patches default.prop to set ro.zygote=zygote64 before Android starts. This bypasses the 32-bit zygote crash under WHPX. The side effect is that 32-bit ABIs are disabled entirely.
IDE disk interface — The disk uses if=ide instead of if=virtio because Android-x86's init script does not load virtio-blk drivers early enough to find the disk at /dev/vda.
e1000 network — The network card uses e1000 instead of virtio-net-pci for broader boot compatibility.
If you want a lightweight Android testing setup for Expo or React Native:
Physical device (recommended) — Scan the QR code from the Expo CLI. The app loads over your local network in seconds. No virtualization, no boot time, real GPU, real touch input.
Official Android emulator with a minimal AVD — Create a device without Google Play, 2GB RAM cap, animations disabled. Still heavier than Mobiler in binary size, but actually usable for development.
BlueStacks / LDPlayer — Gaming emulators with real GPU passthrough and fast boot. Not developer-oriented, but fine for quick visual checks.
src/
components/
controls/ # VM start/stop controls
display/ # VNC canvas viewer
setup/ # First-run setup wizard
status/ # VM status indicators
store/
vm-store.ts # VM state (Zustand)
settings-store.ts # Persistent settings
sensor-store.ts # Sensor simulation state
App.tsx
src-tauri/src/
qemu/
manager.rs # QEMU process lifecycle
config.rs # VM configuration and QEMU args
accel.rs # Hardware acceleration detection (WHPX/HVF/KVM/TCG)
adb/
bridge.rs # ADB connection
commands.rs # ADB shell commands
port_forward.rs
display/
scrcpy_server.rs # scrcpy server push/start (partial)
expo/
detect.rs # Metro bundler detection
setup.rs # Expo Go install/launch
sensors/ # Sensor simulation
commands/ # Tauri IPC command handlers
MIT