Running H5 Games in an Isolated Android Process Without Breaking IPC
Any app embedding WebView-heavy content on Android 13+ will hit the App Freezer: the system freezes background processes so aggressively that synchronous IPC breaks silently. The oneway-AIDL-plus-pending-pool pattern shown here is a concrete, reproducible defense that keeps cross-process state consistent without ANRs or lost callbacks.
A single-process WebView for an H5 game can push memory past 200 MB and turn a JS crash into a full app kill. The fix is a dedicated `:web` subprocess that isolates the memory and crash surface, but Android's power-saving App Freezer suspends that process the moment it loses focus, dropping synchronous Binder calls and causing RemoteCallbackList to unregister callbacks permanently. The architecture described here counters this with oneway AIDL for all main-to-sub notifications, a kernel-level message buffer that replays transactions on thaw, and a dual-channel wake-up protocol where the subprocess forcibly re-registers callbacks on resume or after a cold-start rebuild.
A pending-event pool on the main process side holds every undelivered message until the subprocess checks in. A synchronous blocking lock with a 2-second timeout prevents JS calls from failing during the brief window when AIDL binding is still in flight. WebView lifecycle management uses MutableContextWrapper to swap between ApplicationContext and Activity context, eliminating the strong reference that causes leaks, and a two-tier cache keeps a detached WebView warm for instant re-expansion or reuses a reset instance from an idle pool to avoid 100–300 ms cold-start jank.
A transparent JSBridge proxy means H5 pages call the same APIs with no changes; the subprocess proxy forwards requests to the main process over AIDL. The subprocess stays minimal — no business modules, just rendering — and all page navigation or scheme handling is dispatched back to the main process. When a game closes, a 500 ms delay before Process.killProcess() gives animations and Binder state time to settle, preventing transient crashes during teardown.
The oneway AIDL pattern effectively turns the Binder driver into a free, kernel-level message broker that survives process suspension — a capability many teams overlook and instead build userspace retry logic.
RemoteCallbackList's automatic unregistration is designed for process death, not suspension, which makes it a liability on modern Android where freezing is far more common than killing.
The 500 ms delay before Process.killProcess() is a small, non-obvious detail that prevents a whole class of hard-to-reproduce teardown crashes tied to animation and Binder timing.
Keeping the subprocess deliberately minimal — no business modules, just rendering — is as much a security boundary as a performance one; it limits the blast radius of any WebView compromise.
The synchronous blocking lock for cold-start IPC is a pragmatic tradeoff: it accepts a brief thread block to avoid a cascade of null-pointer errors in JS that would be harder to debug and recover from.