Manage WebView state efficiently

When managing an Android app's lifecycle, preserving user state during background resource reclamation is a core component of a seamless user experience. For apps incorporating web workflows, WebView.saveState(Bundle) lets you serialize a WebView's navigation history and state into a Bundle. This data can subsequently be restored using WebView.restoreState(Bundle).

However, standard implementations can run into transaction size limitations under heavy browsing sessions. This page describes these architectural limitations and provides strategies to prevent memory-related exceptions while maintaining navigation history.

The 1MB transaction limit and state wiping

Android imposes a strict 1MB limit on the total volume of data that can be stored within savedInstanceState. This 1MB budget is shared across the entire app process. If an app incorporates multiple WebView instances, their collective navigation state and history must fit within this single shared allocation. Exceeding this boundary triggers a TransactionTooLargeException, resulting in an app crash.

A common but problematic mitigation strategy involves monitoring the size of the WebView state bundle and completely clearing the WebView history if it crosses an arbitrary safety threshold (such as 300KB). While this prevents a crash, it introduces severe regressions into the user experience:

  • Loss of backward navigation: Android often terminates background app processes to reclaim memory for other tasks. You can use saveState(Bundle) within the onSaveInstanceState() lifecycle callback to preserve navigation history. If you wipe this history to avoid the 1MB transaction limit, the entire navigation stack is lost. When the user returns to the app, the system back button immediately exits the component or app because no historical context remains to support backward navigation, regardless of whether a process restart occurred.

  • BFCache invalidation: Clearing history prevents the app from using the Back-Forward Cache (BFCache), removing the ability to instantly render previously visited pages.

  • Increased latency: Users lose their current state within the WebView, requiring full re-navigation and re-initialization. This process significantly increases network overhead and transaction latency.

Architectural mitigation strategies

To prevent TransactionTooLargeException crashes without degrading the user experience through complete history deletion, you must maintain a rigorous balance between state retention and memory efficiency. By implementing the following optimization strategies, you can safely manage the 1MB transaction budget while preserving essential navigation history and session integrity.

Enforce size limits on state serialization

Instead of completely wiping the navigation stack when it grows too large, a more effective pattern is to truncate the historical data:

  • Targeted drop policy: Use WebViewCompat.saveState() to serialize state while enforcing a specific byte limit (for example, WebViewCompat.saveState(webView, outState, maxSizeBytes)). This API automatically drops older navigation entries sequentially until the total payload fits within your defined allocation. Crucially, this only truncates the serialized Bundle without modifying or clearing the live history of the active WebView, ensuring immediate backward navigation remains completely intact.

  • Forward entry removal: If the application interface provides a back button but lacks a dedicated forward navigation button, you can discard all forward navigation entries by setting the saveState API's includeForwardState parameter to false. This significantly reduces the payload size without impacting the user's available navigation paths.

Manage resource latency with the HTTP Cache Quota API

While saveState manages the 1MB Bundle limit for ephemeral navigation history, the HTTP Cache Quota API provides manual control over persisted web resources (disk cache) on a per-profile basis. This creates a clear distinction between short-term navigation context and long-term cached assets.

Choosing an appropriate quota involves a performance trade-off:

  • Higher quotas improve offline availability and resource loading latency by keeping more assets on disk.
  • Lower quotas minimize the app's disk footprint and prevent OS-led cache eviction of other critical app data.

These settings are persisted across app restarts and must be configured from the main thread.

The following implementation demonstrates how to configure a disk cache quota for the default profile:

Kotlin

if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
    WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
    val defaultProfile = ProfileStore.getInstance()
        .getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME)
    val httpCache = defaultProfile.httpCache

    // Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
    httpCache.setQuotaBytes(50L * 1024 * 1024)
}

Java

if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
    WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
    Profile defaultProfile = ProfileStore.getInstance()
        .getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME);
    HttpCache httpCache = defaultProfile.getHttpCache();

    // Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
    httpCache.setQuotaBytes(50L * 1024 * 1024);
}

For more information about quota sizing strategies, lifecycle management, and profile boundaries, see Manage HTTP cache quota in WebView.

Key performance considerations

The following points highlight the technical limitations and internal data behaviors that govern WebView state behavior:

  • Opaque PageState blobs: Approximately 70% of the data stored by saveState consists of internal PageState blobs from the rendering engine. This data captures granular session states, including form inputs and iframe scroll positions. Avoid attempting to manually parse or strip individual segments from these blobs, as doing so poses severe security risks and breaks session restoration integrity.

  • Granular history management: The standard WebBackForwardList API doesn't natively support the arbitrary removal of individual historical elements. For strict state management, you must implement truncation strategies using the maxSizeBytes and includeForwardState parameters within WebViewCompat.saveState() to ensure architectural safety.