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 e otimização de conexão. Assim, o app pode buscar ou renderizar conteúdo antes que o usuário navegue explicitamente até ele.

A WebView oferece suporte a três tipos principais de carregamento especulativo: pré-conexão, pré-busca e pré-renderização, além de dicas do QUIC para otimizar a negociação de protocolo.

Ao implementar uma estratégia de carregamento especulativo, é possível:

  • 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 e prefira protocolos mais rápidos, como HTTP/3.
  • Taxas de sucesso de navegação mais altas:ao pré-aquecer a rede e o cache, é menos provável que as navegações falhem devido a problemas de rede temporários.
  • Melhor capacidade de resposta percebida:a pré-renderização, em particular, permite transições instantâneas que fazem o app parecer muito mais rápido.

Escolher uma estratégia de carregamento especulativo

O principal diferencial entre essas estratégias está no escopo: as APIs de dicas de pré-conexão e QUIC são baseadas na origem, o que significa que elas só exigem o domínio de destino. As APIs de pré-busca e pré-renderização são baseadas em URL, o que significa que elas exigem o caminho exato da página da Web.

Como as dicas de pré-conexão e QUIC operam no nível da origem, é possível iniciá-las muito antes no ciclo de vida do app, mesmo antes de saber o conteúdo ou a página específica para que 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
Meta principal Aquecer a conexão Fazer cache apenas de 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 do 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 Baixa (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 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 Carga de rede mais rápida para URLs correspondentes Navegação instantânea de verdade após a ativação

Pré-conexão com origens

A pré-conexão acelera os carregamentos futuros realizando buscas DNS e handshakes TCP/TLS ou QUIC 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 fazer a chamada de pré-conexão muito antes da pré-busca e da pré-renderização.

Essa estratégia de baixo recurso e no nível do perfil reduz a latência inicial para qualquer compartilhamento de WebView desse perfil, desde que a origem ainda não tenha sido visitada. A conexão permanece aberta por aproximadamente 30 segundos, beneficiando qualquer solicitação HTTP, navegação ou subrecurso entre origens subsequente ao eliminar a sobrecarga de handshake.

Implementação

Para iniciar uma pré-conexão, chame preconnect(String url) em uma instância de 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 conveniência, um URL completo pode ser fornecido (como https://www.example.com/index.html). Isso é tratado automaticamente como uma chamada à 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
}

Indicar suporte ao protocolo QUIC com dicas de QUIC

O HTTP/3 (que é executado no protocolo de transporte QUIC) oferece melhorias significativas na latência em relação ao HTTP/2, incluindo handshakes de 0-RTT, resiliência de conexão aprimorada e eliminação do bloqueio head-of-line durante a perda de pacotes.

Por padrão, o WebView só tenta uma conexão QUIC se tiver uma indicação de que a origem é compatível com QUIC, como um cabeçalho Alt-Svc ou um registro DNS HTTPS de uma interação anterior. Sem esse conhecimento prévio, a WebView usa HTTP/2 ou HTTP/1.1 para a conexão inicial.

Chamar addQuicHints pré-preenche essas informações de suporte ao protocolo, permitindo que a WebView se conecte usando QUIC imediatamente na primeira conexão com as origens especificadas.

Pré-conexão com dicas do QUIC

Embora as dicas de pré-conexão e QUIC sejam otimizações no nível da origem em um Profile, elas têm funções distintas e complementares:

  • preconnect:abre e mantém ativamente uma conexão de rede (pesquisa de DNS e handshake TCP/TLS ou QUIC) por aproximadamente 30 segundos. Como mantém conexões de rede ativas abertas, ele consome recursos de dispositivo e de rede e deve ser reservado para origens de destino de alta probabilidade.
  • addQuicHints:não gera tráfego de rede imediato. Ele atualiza as propriedades do servidor na memória da pilha de rede Profile para registrar o suporte ao protocolo. Como ele tem uma sobrecarga insignificante, você pode configurar dicas de QUIC durante a inicialização do app para todas as origens conhecidas compatíveis com HTTP/3.

Para um desempenho ideal, chame addQuicHints antes de chamar preconnect, prefetchUrlAsync ou loadUrl. Isso garante que qualquer pré-conexão ou solicitação de página subsequente negocie o HTTP/3 desde o início.

Implementação

Para configurar dicas de QUIC, chame addQuicHints(Set<String> urls) em uma instância Profile. É necessário chamar essa API na linha de execução de interface e verificar se o WebView é compatível com o recurso WebViewFeature.ADD_QUIC_HINTS_V1.

Assim como preconnect, addQuicHints opera em origens, mas URLs completos podem ser fornecidos (como https://www.example.com/index.html) e são normalizados automaticamente para a origem (https://www.example.com).

O método é aditivo: chamá-lo várias vezes mescla as origens fornecidas em todo o Profile.

Kotlin

// Must be called on the @UiThread
@OptIn(Profile.ExperimentalAddQuicHints::class)
if (WebViewFeature.isFeatureSupported(WebViewFeature.ADD_QUIC_HINTS_V1)) {
    val quicOrigins = setOf(
        "https://www.example.com",
        "https://api.example.com"
    )
    profile.addQuicHints(quicOrigins)
    // WebView now prioritizes HTTP/3 over QUIC for connections to these origins
}

Java

// Must be called on the @UiThread
// Requires @Profile.ExperimentalAddQuicHints annotation or suppression
if (WebViewFeature.isFeatureSupported(WebViewFeature.ADD_QUIC_HINTS_V1)) {
    Set<String> quicOrigins = new HashSet<>(Arrays.asList(
        "https://www.example.com",
        "https://api.example.com"
    ));
    profile.addQuicHints(quicOrigins);
    // WebView now prioritizes HTTP/3 over QUIC for connections to these origins
}

Configuração comum: PrefetchParameters e PrerenderParameters

A pré-busca e a pré-renderização usam PrefetchParameters ou PrerenderParameters para personalizar a solicitação. Com essas classes, é possível 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 em cache.

Implementação

Para iniciar um pré-busca, chame prefetchUrlAsync() em uma instância Profile. Essa operação aceita apenas o 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 da interceptação

A solicitação de pré-busca da WebView muda quando e como o callback shouldInterceptRequest() é acionado. Como isso tem um impacto direto no uso bem-sucedido do conteúdo pré-buscado, é 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 em 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 esta 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 até o URL (por exemplo, usando WebViewCompat.navigate ou 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:a WebView vai acionar shouldInterceptRequest() para o HTML principal neste momento. Para veicular a página do cache de pré-busca com sucesso, seu interceptor precisa retornar null. Se você retornar um WebResourceResponse personalizado, a WebView vai respeitar seu interceptador e ignorar completamente o cache de pré-busca.

  • Avaliação de sub-recursos:depois que o HTML pré-buscado é liberado para uso, o 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 a 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 adequada verificando o seguinte:
    • Cookies atuais:para proteger a privacidade do usuário e evitar efeitos colaterais semelhantes a CSRF, a 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, a WebView poderá adiar o gerenciador de busca do service worker em vez de iniciar uma pré-busca de rede padrão.
    • Disponibilidade de proxy:a 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 CancellationSignal para encerrar uma solicitação em andamento e evitar 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, a WebView primeiro vai fazer 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 da linha de execução de 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, a WebView impõe as seguintes restrições de tempo de execução:

  • Pressão de memória:a 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 fornecido apenas para a navegação pretendida.

Correspondência exata x correspondência de 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 do cache. Se os parâmetros de consulta forem diferentes, a WebView usará as seguintes regras de pesquisa sem variação (NVS, na sigla em inglês):

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

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

Configuração global

Ajuste o comportamento de carregamento especulativo no nível do perfil configurando limites de PrefetchCache e pré-renderizações máximas. Também é possível redefinir os limites personalizados de pré-busca 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 gerenciador 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íncrona.
  • PrefetchNetworkException:indica uma falha no nível da rede ou do servidor. Ele pode incluir um campo httpStatusCode (como 404 ou 503) para ajudar a diagnosticar problemas do lado do servidor.
  • PrerenderException:a superclasse de todos os erros relacionados à pré-renderização, como falhas devido à pressão da 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 e conservar os recursos do sistema:

  • Inicie cedo:comece a pré-busca durante a inicialização do app ou assim que um destino de navegação for provável.
  • Emparelhe dicas do QUIC com pré-aquecimento de conexão:chame addQuicHints() antes de iniciar preconnect(), prefetchUrlAsync() ou navegações padrão para garantir que a WebView tente estabelecer conexões usando HTTP/3.
  • Estratégia integrada:se você pré-renderizar um URL já no cache de pré-busca, a navegação de pré-renderização será veiculada desse cache, evitando solicitações de rede redundantes.
  • Monitore as cotas:a pré-renderização consome muitos recursos. Prefira a pré-busca para vários candidatos prováveis e reserve a pré-renderização para a navegação única mais provável.
  • Suporte a esquemas:verifique se todos os URLs usam o esquema HTTPS obrigatório. Esquemas inválidos ou entradas nulas acionam um IllegalArgumentException síncrono.

Outros recursos

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