The question
When a WebView screen is blank, the instinct is to open DevTools and look at the page. Over two apps, the Teachmint phone app and an AI assistant that runs as a system app on classroom panels, most of my WebView bugs weren’t in the page. They were in how Android creates, sizes, renders and hosts WebView. Some never got as far as loading a URL.
My initial mental model
WebView is a View. You construct it, give it a URL, and the page’s problems are the page’s problems.
WebView is actually a separate, independently updated system package, loaded into your process at runtime, with rules about which processes may load it at all. Each of the five bugs below breaks one of those assumptions.
1. A system app may not use WebView at all
To get system-level permissions without asking each OEM to allowlist them, I made two OEM flavours of the
assistant run as the system user, using android:sharedUserId="android.uid.system" and the
platform certificate. The next day it was reverted.
Running as uid 1000, the first WebView in the process threw:
WebViewFactory: WebView is not allowed in privileged processes
It’s a deliberate security rule. WebView renders untrusted web content, so Android refuses to load it into the system user’s processes. The chat’s link viewer and the YouTube player both crashed. The fix was to go back to a normal per-app uid, as a platform-signed privileged app, and get permissions the ordinary way.
The lesson generalises. “Run as system” isn’t a free upgrade. It changes which parts of the platform will work in your process at all, and WebView is the first to refuse.
2. The WebView provider can change under you
WebView is supplied by a separate, updatable package. That has two consequences most apps never see, until they ship on OEM ROMs:
-
It can be missing or disabled. On some devices the provider package is absent or turned off, and
WebView(context)throws. In the phone app, every WebView screen now checks for a provider first:fun isWebViewPackageAvailable(context: Context): Boolean { val provider = WebView.getCurrentWebViewPackage() ?: return false return try { context.packageManager.getPackageInfo(provider.packageName, 0); true } catch (e: PackageManager.NameNotFoundException) { false } }If it fails, the screen shows a fallback with a way to install it, instead of crashing.
-
It can be updated while your process is running. On a panel, the YouTube player’s WebView constructor threw
Resources$NotFoundException: failed to redirect ResourcesImpl. The provider had changed under the running process, and its resources no longer resolved. The fix is unglamorous: construct WebView insidetry, and show a placeholder that opens the video externally. A blank player is better than losing the whole chat.
3. 100vh was zero
Our AI tutor generates small interactive lessons, such as a racing game or a canvas animation, and shows them in a WebView inside Compose. On the panel some of them were blank, even though a server-side check in headless Chrome passed them.
WebView DevTools on the panel showed the cause in one number: 100vh was 0, while
window.innerHeight was 582. Every element sized in viewport units collapsed. The game’s container was
4 pixels tall, and its canvas had height 0.
The WebView was created inside a Compose AndroidView, which gives its child WRAP_CONTENT layout
params by default. A WebView that wraps its content gives Chromium a zero-height layout viewport to
compute vh against, so the page has no viewport height to fill. The fix is one line in the factory:
AndroidView(factory = { ctx ->
WebView(ctx).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
// …
}
})
After it, vh resolved to 582 on the panel and the game rendered and played. This is the bug I’d warn
any Compose developer about. It looks like a broken page, it passes every off-device check, and it’s a
layout-params default.
4. Creating WebView early costs every feature that needs an Activity
In the phone app, opening a web feature cold meant starting Chromium and then loading the page. I tried
the obvious optimisation: create and warm the WebView in Application.onCreate, before any screen
needed it.
It was reverted the next day. A WebView created in Application has no Activity, so the manager’s
activity reference had to become nullable, and so did everything that needs one:
- fullscreen video, through
onShowCustomView - new windows, through
onCreateWindow - the file chooser
- camera capture
Each of those became a silent failure path. The better answer to cold loads turned out to be keeping WebViews that had already loaded alive across navigation, in a small LRU cache.
5. A software layer breaks video
A WebView in the phone app was switched to a software layer to work around a rendering problem. Two months later it was switched back, with the commit message “due to video playing issue”. A software layer turns off hardware acceleration for the view, and video and accelerated canvases depend on it. It’s a lever to pull only with a test for every kind of content the page shows, and video should be first on that list.
Practical consequences
A checklist I now run for any WebView screen:
| Check | Why |
|---|---|
Not running as android.uid.system |
WebView refuses privileged processes |
| Provider present and enabled, before construction | It can be missing or disabled |
Constructor inside try, with a fallback UI |
The provider can change under a running process |
MATCH_PARENT inside Compose AndroidView |
WRAP_CONTENT gives the page a zero-height viewport |
An Activity behind the WebView |
Fullscreen, new windows and pickers need one |
| Hardware layer, and a video test before changing it | Software layers break video and accelerated canvas |
What I would remember
- WebView is a separate, updatable package loaded at runtime. Treat creating it as fallible.
- The system uid is the one place WebView won’t run.
- Inside Compose, set WebView’s layout params explicitly, or
vhmay be zero. - When a page works everywhere except your app, suspect the host before the page.
- Faster creation isn’t worth losing the
Activity. Cache loaded WebViews instead.
Further reading
- WebView and
WebView.getCurrentWebViewPackage. - Inspect WebViews with Chrome DevTools.
- Interoperability APIs:
AndroidView. - Hardware acceleration.