Carregamento especulativo no WebView

A latência de navegação é uma métrica essencial para a experiência do usuário. Para ajudar os desenvolvedores a reduzir essa latência, o WebView oferece APIs para carregamento especulativo, permitindo que o app busque ou renderize conteúdo antes que o usuário navegue explicitamente até ele.

O WebView oferece suporte a três tipos principais de carregamento especulativo: pré-conexão, pré-busca e pré-renderização.

Ao implementar uma estratégia de carregamento especulativo, você pode conseguir o seguinte:

  • Redução significativa na latência de carregamento de conteúdo da Web:mova o horário de início da rede para mais cedo no ciclo de vida do app.
  • Taxas de sucesso de navegação mais altas:ao pré-aquecer a rede e o cache, as navegações têm menos probabilidade de falhar devido a problemas de rede temporários.
  • Melhor capacidade de resposta percebida:a pré-renderização, em particular, permite transições instantâneas que tornam o app significativamente mais rápido.

Escolher uma estratégia de carregamento especulativo

O principal diferenciador entre essas estratégias está no escopo delas: a API Preconnect é baseada na origem, o que significa que ela só exige o domínio de destino. As APIs Prefetch e Prerender são baseadas em URL, o que significa que elas exigem o caminho exato da página da Web.

Como a pré-conexão opera no nível de origem, ela pode ser iniciada muito mais cedo no ciclo de vida do app, mesmo antes de você saber o conteúdo ou a página específica para a qual o usuário vai navegar.

A tabela a seguir compara essas três estratégias para ajudar você a escolher a certa para seu caso de uso:

Recurso Pré-conexão Pré-busca Pré-renderização
Objetivo principal Aquecer a conexão Armazenar em cache apenas HTML (sem JavaScript ou CSS) Pré-renderizar a página inteira
Scope Nível de perfil (compartilhado entre WebViews) Nível de perfil (compartilhado entre WebViews) Nível de WebView (vinculado a um WebView específico)
API Jetpack WebKit androidx.webkit.Profile androidx.webkit.Profile androidx.webkit.WebViewCompat
Métodos principais da API preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
Configuração N/A PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
Uso de recursos Baixo (rede) Médio (rede, memória) Alto (CPU, memória, rede)
Quando usar Quando a origem de destino é conhecida, mas o URL específico ainda não foi determinado. Quando o URL exato é conhecido e a navegação é provável, com o armazenamento em cache compartilhado entre WebViews. Quando o URL exato é conhecido e a navegação é altamente certa em um WebView específico.
Benefícios Configuração de conexão mais rápida para qualquer URL na origem Carregamento de rede mais rápido para URLs correspondentes Navegação realmente instantânea após a ativação

Pré-conexão com origens

A pré-conexão acelera os carregamentos futuros, realizando pesquisas de DNS e handshakes TCP/TLS de forma preventiva para uma origem especificada.

Ao contrário da pré-busca e da pré-renderização, que exigem um URL de destino exato, a pré-conexão é estritamente baseada na origem. Isso permite que você faça a chamada de pré-conexão muito mais cedo do que a pré-busca e a pré-renderização.

Essa estratégia de baixo recurso e nível de perfil reduz a latência inicial de qualquer WebView que compartilhe esse perfil, desde que a origem ainda não tenha sido visitada. A conexão permanece aberta por aproximadamente 30 segundos, beneficiando todas as solicitações HTTP, navegações ou sub-recursos de origem cruzada subsequentes, eliminando a sobrecarga de handshake.

Implementação

Para iniciar uma pré-conexão, chame preconnect(String url) em uma instância Profile. Essa API precisa ser chamada na linha de execução da interface e exige que o WebViewFeature.PRECONNECT seja compatível.

A API opera na origem, mas, para maior conveniência, um URL completo pode ser fornecido (como https://www.example.com/index.html). Isso é tratado automaticamente como uma chamada para a origem (por exemplo, https://www.example.com). Várias origens podem ser conectadas chamando essa API várias vezes.

Kotlin

// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
    profile.preconnect("https://www.example.com/index.html")
    // This initiates a connection to the origin https://www.example.com
}

Java

// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
    profile.preconnect("https://www.example.com/index.html");
    // This initiates a connection to the origin https://www.example.com
}

Configuração comum: PrefetchParameters e PrerenderParameters

A pré-busca e a pré-renderização usam PrefetchParameters ou PrerenderParameters para personalizar a solicitação. Essas classes permitem fornecer cabeçalhos e dicas adicionais para correspondência de URL, como configurações No-Vary-Search.

Kotlin

// Isolated configuration specifically for Cache-Level Prefetching
val prefetchParams = PrefetchParameters.Builder()
    .addAdditionalHeader("X-Custom-Client", "Android-App-V2")
    .setExpectedNoVarySearchHeader(
        NoVarySearchHeader.varyExcept(true, listOf("session_id", "click_ref"))
    )
    .build()

Java

PrefetchParameters prefetchParams = new PrefetchParameters.Builder()
    .addAdditionalHeader("X-Custom-Header", "value")
    /**
     * Hint to ignore specific query parameters during cache matching.
     * This allows the cache to match even if the tracking_id differs.
     */
    .setExpectedNoVarySearchHeader(
        NoVarySearchHeader.varyExcept(true, Arrays.asList("tracking_id"))
    )
    /**
     * Determines if Client Hints are sent.
     * NOTE: This is ignored for Prerendering API requests, which default to
     * the WebView's WebSettings.getJavaScriptEnabled() value.
     */
    .setJavaScriptEnabled(true)
    .build();

Pré-busca de conteúdo

A pré-busca faz o download do recurso HTML principal de um URL e o armazena no cache de rede do perfil. No WebView, um Profile atua como um contêiner para dados do navegador, incluindo cookies, cache HTTP e service workers. Como a pré-busca é uma operação no nível do perfil, qualquer WebView associado a esse perfil pode aproveitar a resposta armazenada em cache.

Implementação

Para iniciar uma pré-busca, chame prefetchUrlAsync() em uma instância Profile. Essa operação só oferece suporte ao esquema HTTPS.

Kotlin

profile.prefetchUrlAsync(
    url,
    prefetchParams,
    cancellationSignal,
    executor,
    object : WebViewOutcomeReceiver<PrefetchResult, PrefetchException> {
        override fun onResult(result: PrefetchResult) {
            if (result.wasDuplicate()) {
                // URL and No-Vary-Search permutations already exist in the cache layer
            } else {
                // The HTML payload has been successfully secured in the HTTP cache
            }
        }

        override fun onError(error: PrefetchException) {
            when (error) {
                is PrefetchNetworkException -> {
                    // Isolates network layer or server-side HTTP anomalies
                    val code = error.httpStatusCode
                    // Facilitates rapid diagnosis of 4xx or 5xx server responses
                }
                else -> {
                    // Catches generalized execution failures and system constraints
                }
            }
        }
    }
)

Java

profile.prefetchUrlAsync(
    url,
    prefetchParams,
    cancellationSignal,
    executor,
    new WebViewOutcomeReceiver<PrefetchResult, PrefetchException>() {
        @Override
        public void onResult(PrefetchResult result) {
            if (result.wasDuplicate()) {
                // URL and No-Vary-Search permutations already exist in the cache layer
            } else {
                // The HTML payload has been successfully secured in the HTTP cache
            }
        }

        @Override
        public void onError(PrefetchException error) {
            if (error instanceof PrefetchNetworkException) {
                // Isolates network layer or server-side HTTP anomalies
                int code = ((PrefetchNetworkException) error).httpStatusCode;
                // Facilitates rapid diagnosis of 4xx or 5xx server responses
            } else {
                // Catches generalized execution failures and system constraints
            }
        }
    }
);

Ciclo de vida de interceptação

A solicitação de pré-busca do WebView altera quando e como o callback shouldInterceptRequest() é acionado. Como isso tem um impacto direto sobre se o conteúdo pré-buscado é usado com sucesso, é fundamental entender o ciclo de vida de duas etapas:

Diagrama mostrando o ciclo de vida da interceptação de pré-busca da WebView em duas etapas
  durante as fases especulativa e de navegação.
Figura 1. O ciclo de vida de interceptação de duas etapas para solicitações e navegações de pré-busca do WebView.

1. A fase especulativa (solicitação de pré-busca)

Quando prefetchUrlAsync() é invocado, o WebView faz o download do recurso HTML principal em segundo plano. shouldInterceptRequest() é completamente ignorado para essa solicitação em segundo plano. Qualquer lógica personalizada, tokens de autorização ou injeções de cabeçalho normalmente processados no interceptor não são aplicados ao recurso HTML pré-buscado.

2. A fase de navegação (ativação do usuário)

Quando o app navega explicitamente para o URL (por exemplo, usando loadUrl()) ou o usuário clica em um link correspondente, o WebView determina se pode usar o cache pré-buscado:

  • Avaliação principal de HTML:o WebView vai acionar shouldInterceptRequest() para o HTML principal neste momento. Para veicular a página do cache de pré-busca, o interceptor precisa retornar null. Se você retornar um WebResourceResponse personalizado, o WebView vai respeitar o interceptor e ignorar completamente o cache de pré-busca.

  • Avaliação de sub-recursos:depois que o HTML pré-buscado é liberado para uso, shouldInterceptRequest() é acionado normalmente para todos os sub-recursos subsequentes (como imagens, scripts e CSS) necessários para concluir a renderização da página.

Comportamentos principais

As seguintes características operacionais e verificações de qualificação regem como o WebView inicia e gerencia solicitações de pré-busca:

  • Segurança de linhas de execução:as solicitações podem ser iniciadas em qualquer linha de execução.
  • Qualificação:antes de iniciar uma busca, o WebView garante que a solicitação seja segura e contextualmente apropriada, verificando o seguinte:
    • Cookies atuais:para proteger a privacidade do usuário e evitar efeitos colaterais semelhantes a CSRF, o WebView pode ignorar a pré-busca se a solicitação exigir cookies autenticados específicos que possam acionar uma mudança de estado no servidor.
    • Presença do service worker:se um service worker já estiver controlando o escopo do URL, o WebView poderá adiar para o handler de busca do service worker em vez de iniciar uma pré-busca de rede padrão.
    • Disponibilidade de proxy:o WebView verifica se o caminho de rede atual (incluindo proxies configurados) está estável para evitar falhas em solicitações especulativas em configurações de rede complexas.
  • Se uma pré-busca não for iniciada (mesmo com parâmetros válidos), geralmente é porque o WebView determinou que uma solicitação em segundo plano pode interferir na sessão atual ou no estado de segurança do usuário.
  • Cancelamento:use o CancellationSignal para encerrar uma solicitação em andamento e impedir que ela seja armazenada em cache.

Pré-renderização de páginas

A pré-renderização cria "conteúdos da Web" ocultos para renderizar totalmente uma página em segundo plano, incluindo a execução de scripts e a busca de sub-recursos. A pré-renderização depende da mesma infraestrutura subjacente da pré-busca. Se um app iniciar uma pré-renderização, o WebView primeiro vai realizar uma pré-busca da resposta para veicular a navegação de pré-renderização, evitando atividades de rede redundantes.

Implementação

A pré-renderização é uma operação no nível da instância do WebView. Chame prerenderUrlAsync() usando WebViewCompat na linha de execução da interface.

Kotlin

WebViewCompat.prerenderUrlAsync(
    webView,
    url,
    cancellationSignal,
    executor,
    params,
    object : PrerenderOperationCallback {
        override fun onPrerenderActivated() {
            // Called when the user navigates to the URL and the hidden page is swapped in
        }

        override fun onError(exception: Throwable) {
            // exception is an instance of PrerenderException
            // Handle prerender failure (for example, memory pressure or disallowed JavaScript APIs)
        }
    }
)

Java

WebViewCompat.prerenderUrlAsync(webView, url, cancellationSignal, executor, params, new PrerenderOperationCallback() {
    @Override
    public void onPrerenderActivated() {
        // Called when the user navigates to the URL and the hidden page is swapped in.
    }

    @Override
    public void onError(@NonNull Throwable exception) {
        // Handle prerender failure (for example, resource constraints or disallowed APIs).
    }
});

A pré-busca e a pré-renderização são totalmente assíncronas. prefetchUrlAsync() pode ser chamado de qualquer linha de execução, enquanto prerenderUrlAsync() precisa ser iniciado na linha de execução da interface.

Restrições técnicas

Para equilibrar a navegação instantânea com a integridade do sistema, o WebView aplica as seguintes restrições de execução:

  • Pressão de memória:o WebView cancela URLs pré-renderizados se o dispositivo estiver com pouca RAM.
  • APIs não permitidas:qualquer tentativa do JavaScript de acessar determinadas APIs (por exemplo, reprodução de áudio, alertas) em um contexto em segundo plano vai encerrar imediatamente a pré-renderização.
  • Limite de instâncias:há um limite para o número de URLs pré-renderizados ativos permitidos por WebView.

Correspondência de URL e No-Vary-Search (NVS)

O WebView exige um algoritmo de correspondência confiável para garantir que um recurso pré-carregado seja veiculado apenas para a navegação pretendida.

Correspondência exata x correspondência NVS

Por padrão, a pré-busca e a pré-renderização exigem uma correspondência exata de URL. Se o URL navegado for idêntico ao URL pré-carregado, ele será veiculado imediatamente no cache. Se os parâmetros de consulta forem diferentes, o WebView vai usar as seguintes regras No-Vary-Search (NVS):

  • A dica:os desenvolvedores fornecem uma dica setExpectedNoVarySearchHeader() durante a inicialização. Se o URL navegado corresponder ao URL da solicitação menos os parâmetros sugeridos, o WebView vai bloquear brevemente para aguardar os cabeçalhos reais do servidor.
  • Cabeçalho do servidor:o cabeçalho de resposta NVS do servidor é a autoridade máxima. Se o servidor confirmar que as diferenças de consulta precisam ser ignoradas, a correspondência será veiculada no cache. Caso contrário, o WebView vai voltar para um carregamento de rede a frio.

O No-Vary-Search (NVS) é para uso avançado, e a maioria dos desenvolvedores não precisa dele porque transmite o mesmo URL exato para a pré-busca e loadUrl(). Essa orientação só é necessária se houver diferenças nos parâmetros de consulta entre o URL de pré-busca e o URL loadUrl().

Configuração global

Ajuste o comportamento de carregamento especulativo no nível do perfil configurando os limites de PrefetchCache e as pré-renderizações máximas. Você também pode redefinir os limites de pré-busca personalizados para os padrões do sistema:

Kotlin

// Configure prefetch cache limits
profile.prefetchCache.setMaxPrefetches(10)
profile.prefetchCache.setPrefetchTtlSeconds(60)

// Reset to system defaults when needed
profile.prefetchCache.clearMaxPrefetches()

// Configure maximum active prerenders
profile.setMaxPrerenders(2)

Java

// Configure prefetch cache limits
PrefetchCache prefetchCache = profile.getPrefetchCache();
prefetchCache.setMaxPrefetches(10);
prefetchCache.setPrefetchTtlSeconds(60);

// Reset to system defaults when needed
prefetchCache.clearMaxPrefetches();

// Configure maximum active prerenders
profile.setMaxPrerenders(2);

Tratamento de erros e exceções

As operações especulativas usam um OutcomeReceiverCompat ou PrerenderOperationCallback para informar os resultados.

Exceções principais

Quando uma operação de carregamento especulativo falha, o handler de erros informa um dos seguintes tipos de exceção principais para ajudar a diagnosticar cenários de falha específicos:

  • PrefetchException:a classe de base para todos os erros de pré-busca assíncronos.
  • PrefetchNetworkException:indica uma falha de rede ou de servidor. Ele pode incluir um campo httpStatusCode (como 404 ou 503) para ajudar a diagnosticar problemas do lado do servidor.
  • PrerenderException:a superclasse para todos os erros relacionados à pré-renderização, como falhas devido à pressão de memória ou ao uso de APIs não permitidas (como reprodução de áudio) em segundo plano.

Estratégias de otimização

Siga estas recomendações para maximizar os benefícios do carregamento especulativo, conservando os recursos do sistema:

  • Iniciar cedo:comece a pré-busca durante a inicialização do app ou assim que um destino de navegação for provável.
  • Estratégia integrada:se você pré-renderizar um URL que já está no cache de pré-busca, a navegação de pré-renderização será veiculada nesse cache, evitando solicitações de rede redundantes.
  • Monitorar cotas:a pré-renderização exige muitos recursos. Prefira a pré-busca para vários candidatos prováveis e reserve a pré-renderização para a navegação mais provável.
  • Suporte a esquemas:verifique se todos os URLs usam o esquema HTTPS obrigatório. Esquemas inválidos ou entradas nulas acionam uma IllegalArgumentException síncrona.

Outros recursos

Para saber mais sobre como depurar apps da Web, otimizar a performance de inicialização do WebView e processar o encerramento do processo de renderização, consulte os seguintes recursos: