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

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

Antes, iniciar navegações de página usando loadUrl tinha limitações significativas:

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

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

  • Substituição de entrada do histórico de navegação:permite substituir a página atual na pilha de histórico WebView.
  • Rastreamento de callback correlacionado:retorna um objeto Navigation que serve como um identificador exclusivo em todas as etapas de um ciclo de vida de navegação.
  • Suporte a cabeçalho de estado salvo:cabeçalhos extras são salvos de maneira 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 o WebViewCompat.navigate, considere as seguintes regras e restrições operacionais:

  • Segurança de linhas de execução:invoque WebViewCompat.navigate na linha de execução da interface (principal).

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

  • Compatibilidade com esquemas de URI:os esquemas de URI padrão (como https: e http:) e personalizados são compatíveis. O esquema javascript: não é compatível.

  • Limite de tamanho do URL:o comprimento máximo da string de URL aceito é 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 acompanhar o ciclo de vida dela, faça o seguinte:

  1. Registre uma implementação de NavigationListener 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 duplicadas de callback.
  2. Construa uma instância NavigationParameters usando NavigationParameters.Builder para especificar comportamentos opcionais, como substituição de histórico ou cabeçalhos HTTP personalizados.
  3. Chame WebViewCompat.navigate, transmitindo sua instância WebView, o URL de destino e os parâmetros.

WebViewCompat.navigate retorna um objeto Navigation que identifica a solicitação de forma exclusiva. Nos callbacks NavigationListener, 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 do 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);
    }
}

Propagar o estado do app usando cabeçalhos HTTP

Os apps da Web geralmente exigem contexto do app Android host para coordenar a lógica de back-end ou personalizar o conteúdo da Web. Adicionar parâmetros de consulta ao URL para transmitir essas informações pode prejudicar a aparência dos URLs, interferir no armazenamento em cache e expor o estado interno do app.

Em vez disso, recomendamos transmitir o contexto do app usando cabeçalhos HTTP personalizados. Ao usar WebViewCompat.navigate e NavigationParameters, você pode enviar esses dados com segurança ao seu servidor. Além disso, a WebView preserva esses cabeçalhos durante a restauração do estado, o que garante que o conteúdo da Web permaneça consistente em todas as mudanças de configuração. Essa persistência só se aplica ao usar WebViewCompat.navigate. Se você usa WebView.loadUrl, os cabeçalhos personalizados não são salvos no pacote de estado WebView e são perdidos na restauração.

Casos de uso comuns

Os casos de uso comuns para transmitir o contexto do app host incluem:

  • Versão do app (X-App-Version): transmitir a versão de lançamento do app host (como BuildConfig.VERSION_NAME) ajuda o servidor de back-end a verificar a compatibilidade da ponte JavaScript nativa, restringir recursos ou pedir aos usuários para atualizar apps mais antigos.
  • Plataforma do cliente (X-Client-Platform): ao identificar explicitamente o ambiente host como Android, o servidor pode fornecer uma interface adaptada à plataforma ou links de armazenamento de rotas sem depender da análise da string User-Agent.

Exemplo de implementação

O exemplo a seguir demonstra como transmitir a versão do aplicativo e a plataforma do cliente para um servidor da Web:

Kotlin

// Attach host app metadata so the server can verify compatibility and tailor content
val params = NavigationParameters.Builder()
    .addAdditionalHeaders(
        mapOf(
            "X-App-Version" to BuildConfig.VERSION_NAME,
            "X-Client-Platform" to "Android"
        )
    )
    .build()

// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params)

Java

// Attach host app metadata so the server can verify compatibility and tailor content
Map<String, String> headers = new HashMap<>();
headers.put("X-App-Version", BuildConfig.VERSION_NAME);
headers.put("X-Client-Platform", "Android");

NavigationParameters params = new NavigationParameters.Builder()
    .addAdditionalHeaders(headers)
    .build();

// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", 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 um IllegalArgumentException síncrono. Estas são algumas causas comuns:

  • Transmitir null para parâmetros obrigatórios não nulos (webView, url ou params).
  • Fornecer um esquema de URL sem suporte, 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, falha na resolução de DNS ou erro de SSL), WebViewCompat.navigate ainda vai retornar um objeto Navigation válido.

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

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

Gerenciamento de pacotes de estado de salvamento

Quando você transmite cabeçalhos extras com NavigationParameters, 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 estado salvo Bundle.

Se você precisar restringir o tamanho do pacote para evitar TransactionTooLargeException durante 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 do 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 continua compatível com o método padrão WebView.restoreState.

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

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

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

  • Sempre verifique a compatibilidade de recursos:antes de invocar a API, confirme o suporte de tempo de execução com WebViewFeature.isFeatureSupported para 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 de WebView.

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

  • Monitore o tamanho do estado de salvamento:ao transmitir payloads de cabeçalho grandes, 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 performance, consulte os seguintes guias: