Otimizar a inicialização do WebView

Quando o app usa um WebView pela primeira vez, o sistema realiza tarefas de inicialização específicas. Esse processo de inicialização é pesado. Por padrão, isso acontece implicitamente na thread da interface do usuário na primeira vez que o aplicativo chama muitas APIs nos pacotes android.webkit ou androidx.webkit ou aumenta um layout que contém uma tag WebView.

Por que isso é importante

Como essa inicialização implícita acontece inteiramente na linha de execução principal, ela impede que o app processe a entrada do usuário e aumenta drasticamente o risco de erros "O app não está respondendo" (ANR, na sigla em inglês). Para mais informações sobre como o Android processa o modelo de execução de linha única, consulte Visão geral de processos e linhas de execução.

Acionadores para inicialização implícita

A inicialização implícita pode ser acionada das seguintes maneiras:

  • Programaticamente: chamando APIs como WebSettings.getUserAgentString().
  • Usando layouts: chamar setContentView() ou layoutInflater.inflate() em um recurso XML que inclui um <WebView>.

A inicialização implícita também pode afetar negativamente as métricas de negócios, como o tempo de inicialização do app e o tempo até a primeira exibição. Se a inicialização implícita não for ideal para seu app, use startUpWebView.

Esta página discute como otimizar o desempenho de inicialização do WebView usando a API startUpWebView.

Assumir o controle da inicialização da WebView

Para melhorar o desempenho e minimizar ANRs, use a API startUpWebView disponível na biblioteca Jetpack Webkit. Essa API oferece controle explícito sobre quando a WebView é iniciada. Ele transfere uma quantidade significativa da carga de trabalho de inicialização para uma linha de execução em segundo plano e permite que qualquer trabalho que precise acontecer na linha de execução de interface seja feito em partes, em vez de um grande bloco monolítico. Isso libera sua thread da interface para processar outras tarefas críticas do app em paralelo, reduzindo a chance de bloquear a experiência do usuário.

A API usa o callback androidx.webkit.WebViewOutcomeReceiver, permitindo que você acompanhe as inicializações bem-sucedidas.

Para usar essa API, adicione a biblioteca Jetpack Webkit ao arquivo build.gradle. Verifique se você está usando a versão 1.16.0 ou mais recente:

dependencies {
    implementation("androidx.webkit:webkit:1.16.0")
}

Usar a API startUpWebView

A otimização do fluxo de inicialização depende do momento em que o app precisa mostrar a WebView.

Quando o WebView não está no caminho crítico

Se o app não precisar carregar um WebView imediatamente, você poderá ocultar completamente o custo de inicialização. Chame startUpWebView no início do ciclo de vida do app e aguarde o callback de sucesso ser acionado.

O ideal é esperar o callback antes de chamar outras APIs WebView. Se você acionar startUpWebView, mas não esperar que ele termine antes de tocar em outros componentes WebView, o sistema vai bloquear a linha de execução de interface enquanto aguarda a conclusão da inicialização. Seu app pode ter algum benefício de desempenho com o trabalho em segundo plano já concluído, mas não o máximo possível.

Quando o WebView está no caminho crítico

Se a jornada principal do usuário do app exigir um WebView imediatamente, provavelmente não será possível esperar a conclusão da inicialização do WebView. Nesse cenário, ainda é necessário chamar startUpWebView o mais cedo possível no ciclo de vida do app (como em Application.onCreate), mas não espere o callback ser acionado. Em vez disso, use as APIs WebView diretamente quando elas forem necessárias.

Para aproveitar ao máximo a inicialização assíncrona, adie a instanciação de uma WebView ou a chamada de APIs WebView até que não haja mais operações de linha de execução de interface de caminho crítico para serem executadas, como inflar hierarquias de layout, inicializar outros SDKs ou desenhar o frame inicial.

Se você chamar startUpWebView e invocar imediatamente as APIs WebView depois na linha de execução principal, a linha de execução de interface será bloqueada aguardando a inicialização ser concluída. Nesse cenário, não há benefício de performance.

Se o uso do WebView puder entrar no caminho crítico, mas você não quiser iniciar o WebView por completo, poderá executar seletivamente as tarefas de inicialização do WebView que podem ser executadas em uma linha de execução em segundo plano, liberando a linha de execução da interface para outras tarefas críticas do app. Para isso, use shouldRunUiThreadStartUpTasks(false).

Mais tarde no ciclo de vida do app, você pode chamar startUpWebView novamente com shouldRunUiThreadStartUpTasks(true) para concluir as tarefas de inicialização restantes na linha de execução da interface. Aguardar o callback nesse ponto depende de se o uso do WebView está no caminho crítico.

Exemplo de implementação

A API usa o callback androidx.webkit.WebViewOutcomeReceiver, permitindo que você acompanhe inicializações bem-sucedidas ou processe falhas de diagnóstico.

É seguro chamar startUpWebView várias vezes de diferentes partes do seu app. Recomendamos evitar a implementação de um loop de nova tentativa simples.

O exemplo de código a seguir demonstra como usar a API WebViewCompat.startUpWebView para inicialização assíncrona.

Kotlin

import android.content.Context
import android.util.Log
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewOutcomeReceiver
import androidx.webkit.WebViewStartUpConfig
import androidx.webkit.WebViewStartUpResult
import androidx.webkit.WebViewStartupException
import java.util.concurrent.Executors

fun initializeWebView(context: Context) {
    // 1. Create a startup configuration specifying the background thread
    // that WebView will use to run its initialization tasks.
    val startUpConfig = WebViewStartUpConfig.Builder(
        Executors.newSingleThreadExecutor()
    ).build()

    // 2. Trigger WebView startup asynchronously
    WebViewCompat.startUpWebView(
        context,
        startUpConfig,
        object : WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException> {

            override fun onResult(result: WebViewStartUpResult) {
                // Success: The WebView has finished its background initialization.
                // This callback is guaranteed to be invoked on the UI thread.
                setupWebView()
            }

            override fun onError(error: WebViewStartupException) {
                // Failure: The initialization encountered a startup exception.
                Log.e("WebViewStartup", "Failed to initialize WebView", error)
            }
        }
    )
}

Java

import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewOutcomeReceiver;
import androidx.webkit.WebViewStartUpConfig;
import androidx.webkit.WebViewStartUpResult;
import androidx.webkit.WebViewStartupException;
import java.util.concurrent.Executors;

public void initializeWebView(Context context) {
    // 1. Create the startup configuration specifying the background thread pool
    // to handle internal non-UI initialization processes.
    WebViewStartUpConfig startUpConfig = new WebViewStartUpConfig.Builder(
            Executors.newSingleThreadExecutor()
    ).build();

    // 2. Trigger WebView startup asynchronously
    WebViewCompat.startUpWebView(
            context,
            startUpConfig,
            new WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException>() {

                @Override
                public void onResult(@NonNull WebViewStartUpResult result) {
                    // Success: The WebView has finished its background initialization.
                    // This callback is invoked directly on the UI thread.
                    setupWebView();
                }

                @Override
                public void onError(@NonNull WebViewStartupException error) {
                    // Failure: Handled using the concrete WebViewStartupException
                    Log.e("WebViewStartup", "Failed to initialize WebView", error);
                }
            }
    );
}

Depurar problemas de inicialização assíncrona

Se startUpWebView não gerar os benefícios de desempenho esperados, geralmente é porque a WebView está sendo inicializada implicitamente em outro lugar do app antes da execução da sua chamada. Isso pode ser devido aos seguintes motivos:

  • Bibliotecas de terceiros ou SDKs inicializados no início do ciclo de vida do app.

  • ContentProviders injetado no seu APK que aciona as APIs WebView durante a inicialização do app.

  • Inflações de layout ou chamadas programáticas (como buscar strings de user agent) que ocorrem inesperadamente cedo.

Para ajudar você a diagnosticar onde e por que essas inicializações inesperadas estão acontecendo, o objeto WebViewStartUpResult oferece recursos de auditoria integrados:

  • getUiThreadBlockingStartUpLocations(): retorna uma lista de objetos StartUpLocation que representam locais em que as tarefas de inicialização do WebView bloquearam a linha de execução da interface principal.

  • getNonUiThreadBlockingStartUpLocations(): retorna sites de chamada específicos em que a execução de tarefas de inicialização bloqueou linhas de execução em segundo plano.

Cada StartUpLocation contém um stack trace que pode ser registrado ou inspecionado para encontrar a classe e o método exatos que acionaram a inicialização.

Exemplo de implementação

É possível inspecionar esses locais no callback onResult para auditar o caminho de inicialização:

override fun onResult(result: WebViewStartUpResult) {
    // Check if WebView startup was blocked on the UI thread prior to or during initialization
    val uiBlockingLocations = result.getUiThreadBlockingStartUpLocations()
    if (!uiBlockingLocations.isNullOrEmpty()) {
        for (location in uiBlockingLocations) {
            // Log the stack trace of the call site that triggered the UI-blocking startup
            Log.w("WebViewDebug", "WebView startup blocked the UI thread here:", location.getStack())
        }
    } else {
        Log.i("WebViewDebug", "Excellent! No UI-blocking WebView startup detected.")
    }

    // Check where background initialization tasks were executed
    val backgroundLocations = result.getNonUiThreadBlockingStartUpLocations()
    backgroundLocations?.forEach { location ->
        Log.d("WebViewDebug", "WebView background startup occurred at: ${location.getStack()}")
    }

    setupWebView()
}

Como usar esses dados durante uma auditoria

Ao auditar a inicialização da WebView do seu app, use as seguintes estratégias para analisar os dados de diagnóstico e resolver gargalos de desempenho:

  • Procure rastreamentos de pilha inesperados:se getUiThreadBlockingStartUpLocations() não estiver vazio, confira os rastreamentos de pilha impressos. Se você encontrar classes pertencentes a SDKs de terceiros ou componentes inesperados, encontrou um gargalo de inicialização implícita.

  • Verifique a ordem de chamada:se as saídas de registro mostrarem que uma inicialização implícita ocorreu antes da sua chamada manual de startUpWebView, mova a inicialização de startUpWebView para antes no app ou configure o SDK ofensivo para atrasar as tarefas dependentes da WebView.

Migrar de soluções alternativas anteriores

No passado, talvez você tenha usado soluções alternativas explícitas para forçar a inicialização da WebView em uma linha de execução em segundo plano, como buscar a string do user agent.

Esses workarounds são considerados práticas sem suporte, e o comportamento subjacente pode mudar nas próximas versões. Se o app depender de soluções alternativas explícitas e não documentadas para acionar ou gerenciar a inicialização do WebView, recomendamos usar a API startUpWebView. A API startUpWebView funciona em todas as versões do Android e do WebView compatíveis com a biblioteca Jetpack Webkit.

Usar a implementação do Jetpack Webkit ajuda a garantir um comportamento consistente em todo o ecossistema Android. Uma das principais vantagens dessa API é a resiliência: em dispositivos legados em que as otimizações mais recentes não estão disponíveis, a API mantém a paridade de desempenho com soluções manuais. Isso permite que você adote benefícios modernos de inicialização em dispositivos mais novos sem incorrer em uma penalidade de desempenho em dispositivos mais antigos.

Se você encontrar problemas ou tiver feedback sobre a API startUpWebView, informe um bug no Issue Tracker público.