Navegação na página aprimorada com WebViewCompat.navigate

WebViewCompat.navigate é uma alternativa aprimorada para WebView.loadUrl que oferece controle refinado sobre o carregamento de páginas da Web, o gerenciamento do histórico e o rastreamento do ciclo de vida de navegação em WebView.

Anteriormente, iniciar navegações de página usando loadUrl tinha limitações notáveis:

  • Nenhuma substituição de entrada de histórico:não era possível substituir a entrada de histórico atual, o que impossibilitava a navegação para uma nova página sem adicionar uma entrada à pilha de retorno.
  • Callbacks desacoplados:não havia um mecanismo direto para correlacionar uma chamada loadUrl específica com eventos de callback subsequentes no WebViewClient.
  • Cabeçalhos extras não salvos:os cabeçalhos personalizados transmitidos para loadUrl não eram salvos como parte do estado WebView, então eram perdidos ao restaurar o estado.

A API WebViewCompat.navigate resolve esses problemas ao introduzir os seguintes recursos:

  • Substituição de entrada do histórico de navegação:permite substituir a página atual na pilha de histórico do WebView.
  • Rastreamento de callback correlacionado: retorna um objeto Navigation que serve como um identificador exclusivo em todas as fases de um ciclo de vida de navegação.
  • Suporte ao cabeçalho de estado salvo:cabeçalhos extras são salvos de forma confiável no pacote de estado WebView para que possam ser reutilizados na restauração do estado.

Principais recursos e limitações

Antes de adotar WebViewCompat.navigate, considere as seguintes regras e restrições operacionais:

  • Segurança de linhas de execução:é necessário invocar WebViewCompat.navigate na linha de execução da interface (principal).

  • Cancelamento e precedência:as navegações em andamento não podem ser canceladas explicitamente. No entanto, iniciar uma nova chamada navigate no mesmo WebView substitui qualquer navegação ativa.

  • Suporte ao esquema de URI:esquemas de URI padrão (como https: e http:) e personalizados são aceitos. O esquema javascript: não é aceito.

  • Limite de tamanho do URL:o comprimento máximo da string de URL aceita é de 2 MB.

  • Verificação de recursos: sempre verifique a disponibilidade de recursos usando WebViewFeature.isFeatureSupported antes de invocar a API para manter a compatibilidade entre diferentes versões do APK do WebView.

Iniciar a navegação e rastrear o ciclo de vida

Para configurar a navegação e rastrear o ciclo de vida dela, faça o seguinte:

  1. Registre uma NavigationListener implementação usando WebViewCompat.addNavigationListener durante a configuração do WebView para receber callbacks estruturados do ciclo de vida. Registre o listener uma vez (em vez de em cada chamada de navegação) para evitar vazamentos de memória e execuções de callback duplicadas.
  2. Construa uma NavigationParameters instância usando NavigationParameters.Builder para especificar comportamentos opcionais, como substituição de histórico ou cabeçalhos HTTP personalizados.
  3. Chame WebViewCompat.navigate, transmitindo a instância WebView, o URL de destino e os parâmetros.

WebViewCompat.navigate retorna um objeto Navigation que identifica exclusivamente a solicitação. Nos seus NavigationListener callbacks, compare esse objeto com o parâmetro Navigation recebido para rastrear essa navegação específica.

Exemplo de implementação

O exemplo a seguir demonstra como configurar parâmetros de navegação, invocar WebViewCompat.navigate e detectar eventos de ciclo de vida de navegação:

Kotlin

class WebNavigationManager(private val webView: WebView) {
    // Track the navigation instance returned by the API
    private var currentNavigation: Navigation? = null

    init {
        // 1. Define listener to observe navigation lifecycle events
        val listener = object : NavigationListener {
            override fun onNavigationStarted(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    // Navigation started
                }
            }

            override fun onNavigationRedirected(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    // Navigation encountered a redirect
                }
            }

            override fun onNavigationCompleted(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    if (navigation.didCommit()) {
                        // Navigation committed successfully
                    } else if (navigation.didCommitErrorPage()) {
                        // Navigation committed an error page
                        val statusCode = navigation.statusCode
                        val error = navigation.webResourceError
                    }
                }
            }

            override fun onFirstContentfulPaintMillis(page: Page, durationMillis: Long) {
                // Match page with current navigation
                if (page == currentNavigation?.page) {
                    // Page rendering started (First Contentful Paint achieved)
                }
            }
        }

        // 2. Register listener on the main thread
        WebViewCompat.addNavigationListener(webView, listener)
    }

    @UiThread
    fun navigateToPage(url: String) {
        // Check feature availability
        if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
            // Fall back to standard loadUrl if navigate API is unavailable
            webView.loadUrl(url)
            return
        }

        // 3. Configure navigation parameters
        val params = NavigationParameters.Builder()
            .setShouldReplaceCurrentEntry(true)
            .addAdditionalHeaders(
                mapOf("X-Test-Navigate-Header" to "TestValue")
            )
            .build()

        // 4. Initiate navigation on the UI thread
        currentNavigation = WebViewCompat.navigate(webView, url, params)
    }
}

Java

public class WebNavigationManager {

    private Navigation mCurrentNavigation;
    private final WebView mWebView;

    public WebNavigationManager(@NonNull WebView webView) {
        mWebView = webView;
        setupListener();
    }

    private void setupListener() {
        // 1. Define listener to observe navigation lifecycle events
        NavigationListener listener = new NavigationListener() {
            @Override
            public void onNavigationStarted(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    // Navigation started
                }
            }

            @Override
            public void onNavigationRedirected(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    // Navigation encountered a redirect
                }
            }

            @Override
            public void onNavigationCompleted(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    if (navigation.didCommit()) {
                        // Navigation committed successfully
                    } else if (navigation.didCommitErrorPage()) {
                        // Navigation committed an error page
                        int statusCode = navigation.getStatusCode();
                        WebResourceErrorCompat error = navigation.getWebResourceError();
                    }
                }
            }

            @Override
            public void onFirstContentfulPaintMillis(@NonNull Page page, long durationMillis) {
                if (mCurrentNavigation != null && page.equals(mCurrentNavigation.getPage())) {
                    // Page rendering started (First Contentful Paint achieved)
                }
            }
        };

        // 2. Register listener on the main thread
        WebViewCompat.addNavigationListener(mWebView, listener);
    }

    @UiThread
    public void navigateToPage(@NonNull String url) {
        // Check feature availability
        if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
            // Fall back to standard loadUrl if navigate API is unavailable
            mWebView.loadUrl(url);
            return;
        }

        // 3. Configure navigation parameters
        NavigationParameters params = new NavigationParameters.Builder()
            .setShouldReplaceCurrentEntry(true)
            .addAdditionalHeaders(Collections.singletonMap(
                "X-Test-Navigate-Header", "TestValue"
            ))
            .build();

        // 4. Initiate navigation on the UI thread
        mCurrentNavigation = WebViewCompat.navigate(mWebView, url, params);
    }
}

Modos de falha e tratamento de erros

A API WebViewCompat.navigate oferece mecanismos distintos para lidar com erros de configuração e falhas de navegação em tempo de execução:

Exceções de argumento inválido

A transmissão de argumentos inválidos aciona uma IllegalArgumentException síncrona. As causas comuns incluem o seguinte:

  • Transmitir null para parâmetros obrigatórios não nulos (webView, url ou params).
  • Fornecer um esquema de URL não aceito, como javascript:.
  • Transmitir chaves ou valores de cabeçalho HTTP malformados que não estão em conformidade com as especificações da RFC 2616.

Se ocorrer uma falha durante a solicitação de rede ou o carregamento de página (como um código de status HTTP 404 status code, falha de resolução de DNS ou erro SSL), WebViewCompat.navigate ainda retornará um objeto Navigation válido.

Quando a navegação terminar, inspecione os seguintes métodos na Navigation instância dentro do seu onNavigationCompleted callback para diagnosticar a falha:

  • getStatusCode: retorna o código de status da resposta HTTP (por exemplo, 404 ou 500).
  • getWebResourceError: retorna um objeto WebResourceErrorCompat detalhando erros de rede, como tempos limite de conexão ou falhas de pesquisa de host.
  • didCommitErrorPage: indica se o WebView confirmou e exibiu uma página de erro para o usuário.
  • didCommit: indica se a navegação foi confirmada com sucesso em uma página de destino sem ser interrompida.

Salvar o gerenciamento de pacotes de estado

Ao transmitir cabeçalhos extras com NavigationParameters, o WebView salva esses cabeçalhos no pacote de estado salvo para que possam ser reutilizados na restauração do estado. No entanto, grandes coleções de cabeçalhos podem aumentar substancialmente o tamanho do Bundle de estado salvo.

Se você precisar restringir o tamanho do pacote para evitar TransactionTooLargeException durante as salvamentos de estado do Android, use WebViewCompat.saveState. Esse método permite definir um limite máximo de tamanho do pacote em bytes e, opcionalmente, excluir itens de histórico de encaminhamento:

Kotlin

// Save state with a maximum bundle size limit (for example, 64 KB)
val maxSizeBytes = 64 * 1024
val includeForwardState = false
val outState = Bundle()

WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState)

Java

// Save state with a maximum bundle size limit (for example, 64 KB)
int maxSizeBytes = 64 * 1024;
boolean includeForwardState = false;
Bundle outState = new Bundle();

WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState);

O pacote resultante permanece compatível com o método padrão WebView.restoreState.

Recomendações de migração e implementação

Para garantir o desempenho e a estabilidade ideais ao navegar no WebView, siga estas recomendações:

  • Migrar de loadUrl para navigate: migre todas as chamadas legadas WebView.loadUrl para WebViewCompat.navigate. Isso garante o gerenciamento uniforme do histórico e que os cabeçalhos sejam sempre salvos como parte do estado salvo.

  • Sempre verifique o suporte a recursos:antes de invocar a API, confirme o suporte em tempo de execução com WebViewFeature.isFeatureSupported para se proteger contra versões mais antigas do WebView.

  • Correlacionar instâncias de navegação:use o objeto Navigation retornado para diferenciar navegações simultâneas ou filtrar callbacks ao gerenciar várias instâncias WebView.

  • Registre o listener uma vez durante a inicialização: como WebViewCompat.addNavigationListener adiciona um listener em vez de substituir um já existente, registre o NavigationListener uma vez durante WebView configuração para evitar vazamentos de memória e execuções de callback duplicadas em navegações subsequentes.

  • Monitore o tamanho do estado salvo:ao transmitir grandes payloads de cabeçalho, use WebViewCompat.saveState com limites de tamanho explícitos para evitar salvar dados de estado excessivos.

Outros recursos

Para saber mais sobre recursos da Web incorporados e otimização de desempenho, consulte os seguintes guias: