跪拜 Guibai
← All articles
Architecture · Android Studio · APP

Running H5 Games in an Isolated Android Process Without Breaking IPC

By 红尘伴蝶舞 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
Running WebView in a separate `:web` process prevents a single H5 game crash from taking down the entire app and lets the OS reclaim all WebView memory on process exit.
Android's App Freezer (SDK 37+) suspends a background subprocess so thoroughly that synchronous Binder calls block, time out, or throw DeadObjectException.
Declaring all main-to-sub notification AIDL interfaces as `oneway` makes calls non-blocking; the Binder driver queues them in the kernel and delivers them when the subprocess thaws.
RemoteCallbackList automatically drops callbacks on DeadObjectException, so a thawed subprocess must forcibly re-register itself via an explicit handshake on onResume or after AIDL service reconnection.
A pending-event pool on the main process stores every undelivered message and replays it once the subprocess checks in, closing the state gap whether the process was frozen or killed and rebuilt.
During cold start, a synchronous blocking lock with a 2-second timeout holds JS calls until AIDL binding completes, preventing null returns that break H5 business logic.
MutableContextWrapper swaps WebView's baseContext between ApplicationContext and Activity, severing the strong reference that causes Activity leaks.
A two-tier WebView cache keeps a detached instance warm for instant re-expansion and reuses reset instances from an idle pool, eliminating 100–300 ms creation overhead.
A transparent JSBridge proxy in the subprocess forwards API calls to the main process over AIDL, requiring zero changes to H5 frontend code.
Subprocess suicide is delayed by 500 ms after logical cleanup to avoid animation stutter, Binder interruption errors, and AMS state races.
Android 9+ requires WebView.setDataDirectorySuffix() to assign a unique data directory per process; sharing one directory throws a RuntimeException.
Conclusions

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.

Concepts & terms
oneway AIDL
An AIDL keyword that makes a cross-process call asynchronous and non-blocking. The call returns immediately after writing to the Binder buffer; the kernel queues the transaction and delivers it when the target process is available, making it resilient to process freezing.
App Freezer
An Android power-saving mechanism (active on SDK 37+) that suspends background application processes by sending SIGSTOP, freezing all their threads. This causes synchronous Binder calls to block or fail if not handled with asynchronous patterns.
RemoteCallbackList
An Android IPC utility class for managing remote callback interfaces across processes. It automatically unregisters callbacks when it detects the peer process has died via DeadObjectException, but this behavior misfires when a process is merely frozen rather than killed.
MutableContextWrapper
An Android context wrapper whose base context can be replaced at runtime. Used here to swap a WebView's context between ApplicationContext (safe for background retention) and an Activity (needed for UI attachment), preventing memory leaks.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗