Gestire in modo efficiente lo stato di WebView

Quando gestisci il ciclo di vita di un'app Android, la conservazione dello stato dell'utente durante il recupero delle risorse in background è un componente fondamentale di un'esperienza utente fluida. Per le app che incorporano workflow web, WebView.saveState(Bundle) ti consente di serializzare la cronologia di navigazione e lo stato di un oggetto WebView in un oggetto Bundle. Questi dati possono essere ripristinati in un secondo momento utilizzando WebView.restoreState(Bundle).

Tuttavia, le implementazioni standard possono riscontrare limitazioni delle dimensioni delle transazioni durante le sessioni di navigazione intense. Questa pagina descrive queste limitazioni architetturali e fornisce strategie per prevenire le eccezioni correlate alla memoria mantenendo la cronologia di navigazione.

Il limite di transazione di 1 MB e la cancellazione dello stato

Android impone un limite rigoroso di 1 MB sul volume totale di dati che possono essere archiviati in savedInstanceState. Questo budget di 1 MB è condiviso tra l'intero processo dell'app. Se un'app incorpora più istanze WebView, il relativo stato e la cronologia di navigazione collettivi devono rientrare in questa singola allocazione condivisa. Il superamento di questo limite attiva un'eccezione TransactionTooLargeException, causando l'arresto anomalo dell'app.

Una strategia di mitigazione comune, ma problematica, prevede il monitoraggio delle dimensioni del bundle di stato di WebView e la cancellazione completa della cronologia di WebView se supera una soglia di sicurezza arbitraria (ad esempio 300 KB). Sebbene ciò impedisca un arresto anomalo, introduce gravi regressioni nell'esperienza utente:

  • Perdita della navigazione indietro: Android spesso termina i processi delle app in background per recuperare memoria per altre attività. Puoi utilizzare saveState(Bundle) all'interno del callback del ciclo di vita onSaveInstanceState() per conservare la cronologia di navigazione. Se cancelli questa cronologia per evitare il limite di transazione di 1 MB, l'intero stack di navigazione viene perso. Quando l'utente torna all'app, il pulsante Indietro del sistema esce immediatamente dal componente o dall'app perché non rimane alcun contesto storico a supporto della navigazione indietro, indipendentemente dal fatto che si sia verificato o meno un riavvio del processo.

  • Invalidazione di BFCache: la cancellazione della cronologia impedisce all'app di utilizzare la cache back-forward (BFCache), rimuovendo la possibilità di eseguire il rendering immediato delle pagine visitate in precedenza.

  • Aumento della latenza: gli utenti perdono il loro stato attuale all'interno di WebView, il che richiede una ri-navigazione e una ri-inizializzazione complete. Questo processo aumenta notevolmente l'overhead di rete e la latenza delle transazioni.

Strategie di mitigazione architetturale

Per evitare arresti anomali di TransactionTooLargeException senza compromettere l' esperienza utente tramite l'eliminazione completa della cronologia, devi mantenere un equilibrio rigoroso tra la conservazione dello stato e l'efficienza della memoria. Implementando le seguenti strategie di ottimizzazione, puoi gestire in sicurezza il budget di transazione di 1 MB mantenendo la cronologia di navigazione essenziale e l'integrità della sessione.

Applicare limiti di dimensione alla serializzazione dello stato

Anziché cancellare completamente lo stack di navigazione quando diventa troppo grande, un pattern più efficace consiste nel troncare i dati storici:

  • Norma di eliminazione mirata: utilizza WebViewCompat.saveState() per serializzare lo stato applicando un limite di byte specifico (ad esempio, WebViewCompat.saveState(webView, outState, maxSizeBytes)). Questa API elimina automaticamente le voci di navigazione precedenti in sequenza finché il payload totale non rientra nell'allocazione definita. È fondamentale che questa operazione tronchi solo l'oggetto Bundle serializzato senza modificare o cancellare la cronologia live dell'oggetto WebView attivo, garantendo che la navigazione indietro immediata rimanga completamente intatta.

  • Rimozione della voce di inoltro: se l'interfaccia dell'applicazione fornisce un pulsante Indietro ma non un pulsante di navigazione Avanti dedicato, puoi eliminare tutte le voci di navigazione Avanti impostando il parametro saveState dell'API includeForwardState su false. In questo modo, le dimensioni del payload vengono ridotte in modo significativo senza influire sui percorsi di navigazione disponibili per l'utente.

Gestire la latenza delle risorse con l'API HTTP Cache Quota

Mentre saveState gestisce il limite di 1 MB di Bundle per la cronologia di navigazione effimera, l'API HTTP Cache Quota fornisce il controllo manuale delle risorse web persistenti (cache su disco) per ogni profilo. In questo modo, si crea una distinzione chiara tra il contesto di navigazione a breve termine e gli asset memorizzati nella cache a lungo termine.

La scelta di una quota appropriata comporta un compromesso in termini di prestazioni:

  • Quote più elevate migliorano la disponibilità offline e la latenza di caricamento delle risorse mantenendo più asset sul disco.
  • Quote inferiori riducono al minimo l'ingombro su disco dell'app e impediscono l'eliminazione della cache di altri dati critici dell'app da parte del sistema operativo.

Queste impostazioni vengono mantenute tra i riavvii dell'app e devono essere configurate dal thread principale.

La seguente implementazione mostra come configurare una quota della cache del disco per il profilo predefinito:

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);
}

Per saperne di più sulle strategie di dimensionamento delle quote, sulla gestione del ciclo di vita e sui limiti dei profili, consulta Gestire la quota della cache HTTP in WebView.

Considerazioni chiave sul rendimento

I seguenti punti evidenziano le limitazioni tecniche e i comportamenti dei dati interni che regolano il comportamento dello stato di WebView:

  • Blob PageState opachi: circa il 70% dei dati archiviati da saveState è costituito da blob PageState interni del motore di rendering. Questi dati acquisiscono stati di sessione granulari, inclusi gli input dei moduli e le posizioni di scorrimento degli iframe. Evita di tentare di analizzare o rimuovere manualmente i singoli segmenti da questi blob, in quanto ciò comporta gravi rischi per la sicurezza e interrompe l'integrità del ripristino della sessione.

  • Gestione granulare della cronologia: l'API WebBackForwardList standard non supporta in modo nativo la rimozione arbitraria di singoli elementi storici. Per una gestione rigorosa dello stato, devi implementare strategie di troncamento utilizzando i parametri maxSizeBytes e includeForwardState all'interno di WebViewCompat.saveState() per garantire la sicurezza architetturale.