Migração da WebView para o Android 4.4

O Android 4.4 (API de nível 19) introduz uma nova versão da WebView com base no Chromium. Essa mudança faz upgrade do desempenho da WebView e padroniza a compatibilidade com HTML5, CSS3 e JavaScript para que correspondam aos navegadores da Web mais recentes. Todos os apps que usam WebView herdarão esses upgrades ao serem executados no Android 4.4 e versões posteriores.

Este documento descreve outras mudanças da WebView que você precisa conhecer se for definir sua targetSdkVersion para "19" ou posterior.

Observação: se a targetSdkVersion estiver definida como "18" ou anterior, WebView operará no modo quirks para evitar o máximo possível algumas das mudanças de comportamento descritas abaixo e ainda fornecer upgrades de desempenho e de padrões da Web para seu app. Entretanto, lembre-se de que layouts de coluna única e estreita e níveis de zoom padrão não são compatíveis com o Android 4.4, e pode haver outras diferenças de comportamento que não foram identificadas. Por isso, teste o app no Android 4.4 ou versões posteriores, mesmo se você mantiver a targetSdkVersion definida como "18" ou anterior.

Para ajudar você a resolver problemas durante a migração do app para a WebView no Android 4.4, ative a depuração remota pelo Chrome no seu computador chamando setWebContentsDebuggingEnabled(). Esse novo recurso na WebView permite que você inspecione e analise o conteúdo da Web, os scripts e a atividade de rede durante a execução em uma WebView. Para saber mais, consulte Depuração remota no Android.

Mudanças do user agent

Se você exibir conteúdo na sua WebView com base no user agent, lembre-se de que a string do user agent mudou um pouco e agora inclui a versão do Chrome:

    Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36
    (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
    

Se você precisa recuperar o user agent, mas não precisa armazená-lo para seu app ou não quer instanciar WebView, use o método estático, getDefaultUserAgent(). No entanto, se você pretende modificar a string do user agent na WebView, é recomendável usar getUserAgentString().

Multithreading e bloqueio de linhas de execução

Se você chamar métodos na WebView usando qualquer linha de execução que não a da IU do app, isso poderá causar resultados inesperados. Por exemplo, se seu app usa várias linhas de execução, você pode usar o método runOnUiThread() para garantir que o código seja executado na linha de execução de IU:

Kotlin

    runOnUiThread {
        // Code for WebView goes here
    }
    

Java

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // Code for WebView goes here
        }
    });
    

Além disso, nunca bloqueie a linha de execução de IU. Alguns apps podem cometer esse erro enquanto aguardam um callback do JavaScript. Por exemplo, não use um código como este:

Kotlin

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()")
    while (result == null) {
        Thread.sleep(100)
    }
    

Java

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()");
    while(result == null) {
      Thread.sleep(100);
    }
    

Em vez disso, use um novo método, evaluateJavascript(), para executar o JavaScript de forma assíncrona.

Como gerenciar URL personalizado

A nova WebView aplica mais restrições ao solicitar recursos e resolver links que usam um esquema de URL personalizado. Por exemplo, se você implementar callbacks como shouldOverrideUrlLoading() ou shouldInterceptRequest(), a WebView os invocará apenas para URLs válidos.

Se você estiver usando um esquema de URL personalizado ou um URL base e perceber que o app está recebendo menos chamadas desses callbacks ou falhando em carregar recursos no Android 4.4, verifique se as solicitações especificam URLs válidos em conformidade com o RFC 3986.

Por exemplo, a nova WebView pode não chamar seu método shouldOverrideUrlLoading() para links como este:

<a href="showProfile">Show Profile</a>

O resultado do clique do usuário nesse link pode variar:

  • Se você carregou a página chamando loadData() ou loadDataWithBaseURL() com um URL base inválido ou nulo, você não receberá o callback shouldOverrideUrlLoading() para esse tipo de link na página.

    Observação: quando você usa loadDataWithBaseURL() e o URL base é inválido ou definido como nulo, todos os links no conteúdo que você está carregando precisam ser absolutos.

  • Se você carregou a página chamando loadUrl() ou se forneceu um URL base válido com loadDataWithBaseURL(), você receberá o callback shouldOverrideUrlLoading() para esse tipo de link na página, mas o URL que você receber será absoluto em relação à página atual. Por exemplo, o URL que você receber será "http://www.example.com/showProfile" em vez de apenas "showProfile".

Em vez de usar uma string simples em um link, como mostrado acima, você pode usar um esquema personalizado como o seguinte:

<a href="example-app:showProfile">Show Profile</a>

Em seguida, você pode gerenciar esse URL no método shouldOverrideUrlLoading(), desta forma:

Kotlin

    // The URL scheme should be non-hierarchical (no trailing slashes)
    const val APP_SCHEME = "example-app:"

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        return if (url?.startsWith(APP_SCHEME) == true) {
            urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
            respondToData(urlData)
            true
        } else {
            false
        }
    }
    

Java

    // The URL scheme should be non-hierarchical (no trailing slashes)
    private static final String APP_SCHEME = "example-app:";

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith(APP_SCHEME)) {
            urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
            respondToData(urlData);
            return true;
        }
        return false;
    }
    

Se não for possível mudar o HTML, você poderá usar loadDataWithBaseURL() e definir um URL base que consista em um esquema personalizado e um host válido, como "example-app://<valid_host_name>/". Exemplo:

Kotlin

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA, null, "UTF-8", null)
    

Java

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,
            null, "UTF-8", null);
    

O nome de host válido precisa estar de acordo com o RFC 3986, e é importante incluir a barra no final. Caso contrário, as solicitações da página carregada podem ser descartadas.

Mudanças na janela de visualização

A propriedade target-densitydpi da janela de visualização não é mais compatível

Anteriormente, a WebView era compatível com uma propriedade da janela de visualização chamada target-densitydpi para ajudar as páginas da Web a especificar a densidade desejada para a tela. Essa propriedade não é mais compatível, e você precisa migrar para o uso de soluções padrão com imagens e CSS, conforme discutido em IU Pixel-Perfect no modo WebView (link em inglês).

A janela de visualização aumenta o zoom quando está pequena

Anteriormente, se você definia a largura da janela de visualização como um valor menor ou igual a "320", ela era definida como "device-width", e se você definia a altura da janela de visualização como um valor menor ou igual à altura da WebView, ela era definida como "device-height". No entanto, ao executar na nova WebView, o valor de largura ou altura é respeitado, e a WebView aumenta o zoom para preencher a largura da tela.

Várias tags da janela de visualização não são compatíveis

Anteriormente, se você incluía várias tags da janela de visualização em uma página da Web, a WebView mesclava as propriedades de todas elas. Na nova WebView, somente a última janela de visualização é usada, e todas as outras são ignoradas.

O zoom padrão se tornou obsoleto

Os métodos getDefaultZoom() e setDefaultZoom() para receber e definir o nível de zoom inicial em uma página não são mais compatíveis, e você precisa definir a janela de visualização adequada na página da Web.

Atenção: essas APIs não são compatíveis com o Android 4.4 ou versões posteriores. Mesmo que a targetSdkVersion esteja definida como "18" ou menos, essas APIs não terão efeito.

Para saber mais sobre como definir as propriedades da janela de visualização no HTML, leia IU Pixel-Perfect no modo WebView.

Se não for possível definir a largura da janela de visualização no HTML, chame setUseWideViewPort() para garantir que a página receba uma janela de visualização maior. Exemplo:

Kotlin

    webView.settings.apply {
        useWideViewPort = true
        loadWithOverviewMode = true
    }
    

Java

    WebSettings settings = webView.getSettings();
    settings.setUseWideViewPort(true);
    settings.setLoadWithOverviewMode(true);
    

Mudanças de estilo

A abreviação do CSS do segundo plano modifica o tamanho do segundo plano

O Chrome e outros navegadores se comportaram dessa forma por algum tempo, mas agora WebView também modifica uma configuração de CSS para background-size se você especificar o estilo background. Por exemplo, o tamanho aqui será redefinido para um valor padrão:

    .some-class {
      background-size: contain;
      background: url('images/image.png') no-repeat;
    }
    

A correção é simplesmente uma alternância entre as duas propriedades.

    .some-class {
      background: url('images/image.png') no-repeat;
      background-size: contain;
    }
    

Os tamanhos estão em pixels CSS em vez de pixels na tela

Anteriormente, os parâmetros de tamanho, como window.outerWidth e window.outerHeight (links em inglês), retornavam um valor em pixels reais da tela. Na nova WebView, um valor com base em pixels CSS é retornado.

Tentar calcular o tamanho físico em pixels para elementos de dimensionamento ou outros cálculos não costuma ser recomendado. No entanto, se você tiver desativado o zoom, e a escala inicial for definida como 1.0, você poderá usar window.devicePixelRatio para descobrir a escala e depois multiplicar o valor do pixel CSS por ela. Você também pode criar uma vinculação JavaScript para consultar o tamanho do pixel pela própria WebView.

Para mais informações, consulte quirksmode.org (link em inglês).

NARROW_COLUMNS e SINGLE_COLUMN não são mais compatíveis

O valor NARROW_COLUMNS para WebSettings.LayoutAlgorithm não é compatível com a nova WebView.

Atenção: essas APIs não são compatíveis com o Android 4.4 ou versões posteriores. Mesmo que a targetSdkVersion esteja definida como "18" ou menos, essas APIs não terão efeito.

Você pode gerenciar essa mudança das seguintes maneiras:

  • Mudar os estilos do aplicativo:

    Se você tem controle do HTML e do CSS na página, mudar o design do seu conteúdo pode ser a abordagem mais confiável. Por exemplo, nas telas em que você cita licenças, recomendamos colar o texto dentro de uma tag <pre>, o que pode ser feito com os seguintes estilos:

    <pre style="word-wrap: break-word; white-space: pre-wrap;">

    Isso pode ser particularmente útil se você não tiver definido as propriedades da janela de visualização para sua página.

  • Usar o novo algoritmo de layout TEXT_AUTOSIZING:

    Se você usava colunas estreitas como uma maneira de tornar uma ampla gama de sites para computador mais legível em dispositivos móveis e não conseguiu mudar o conteúdo do HTML, o novo algoritmo TEXT_AUTOSIZING pode ser uma alternativa adequada a NARROW_COLUMNS.

Além disso, o valor SINGLE_COLUMN, que estava obsoleto, também não é compatível com a nova WebView.

Como gerenciar eventos de toque no JavaScript

Se a página da Web estiver gerenciando diretamente os eventos de toque em uma WebView, você também precisa estar gerenciando o evento touchcancel (link em inglês). touchcancel será chamado em algumas situações, o que pode causar problemas se ele não for recebido:

  • Um elemento é tocado (ou seja, touchstart e touchmove são chamados) e a página é rolada, fazendo com que um touchcancel seja acionado.
  • Um elemento é tocado (touchstart é chamado), mas event.preventDefault() não é chamado, resultando em antecedência suficiente para que touchcancel seja acionado. Portanto, WebView presume que você não quer consumir os eventos de toque.