WebView executa código nativo em vários processos para renderizar conteúdo da Web
no seu app Android. Deixar instâncias WebView não gerenciadas pode levar a vazamentos de memória
, falhas por falta de memória (OOM) e desempenho degradado do app.
Este documento explica o modelo de memória multiprocesso do WebView, descreve como gerenciar corretamente o ciclo de vida dele para evitar vazamentos e fornece fluxos de trabalho práticos para diagnosticar problemas de memória.
Noções básicas sobre a arquitetura de memória do WebView
Para gerenciar a memória do WebView de maneira eficaz, entenda como o Android aloca recursos para conteúdo da Web:
Execução multiprocesso:no Android 8.0 (nível 26 da API) e mais recentes, o
WebViewsepara o conteúdo da Web das funções principais do app em vários processos. Em dispositivos com pouca RAM, ele pode voltar a um único processo:- Processo do host (navegador):o processo principal do app em que o código
Activitye Java ou Kotlin são executados. - Processo de renderização isolado:um processo de sandbox separado (
SandboxedProcessService) que analisa HTML e CSS, executa JavaScript e renderiza páginas da Web.
- Processo do host (navegador):o processo principal do app em que o código
Consumo de memória nativa:a maior parte da
WebViewmemória, incluindo gráficos renderizados, a árvore do DOM e a memória de tempo de execução do JavaScript, é alocada na memória nativa, não no heap Java. Um heap dump Java (.hprof) mostra apenas um objeto wrapper Java leve e não captura a memória real usada pelo conteúdo da Web.Impacto da memória nativa no sistema:ao contrário das alocações de heap Java, que são limitadas pelo limite
maxHeapdo app e falham rapidamente com umOutOfMemoryError, a memória nativa pode crescer silenciosamente para gigabytes. À medida que a memória nativa não liberada preenche a RAM física e o espaço de troca (zRAM), o Low Memory Killer (LMK) do Android começa a encerrar processos em segundo plano para recuperar a memória. Isso degrada a multitarefa geral do dispositivo antes de encerrar o app em primeiro plano.
Gerenciar o ciclo de vida do WebView
O gerenciamento adequado do ciclo de vida é fundamental para evitar vazamentos de memória. Um erro comum é presumir que a remoção de um WebView do layout ou a conclusão automática de uma Activity libera a memória.
Para garantir a limpeza completa das referências de contexto Java e dos recursos de renderização nativos, é necessário orquestrar explicitamente uma sequência de encerramento no ciclo de vida do componente do host (como onDestroy()), interrompendo a execução da página ativa, desanexando a visualização do contêiner e liberando as vinculações nativas.
Limpar instâncias do WebView
Para garantir um desligamento limpo e liberar recursos quando sua Activity ou Fragment for destruída, faça o seguinte:
- Remova o
WebViewdo contêiner pai (ViewGroup). - Interrompa o carregamento ativo e limpe o histórico de navegação.
- Chame
destroy(). - Limpe a referência para
null.
O exemplo a seguir demonstra como limpar corretamente um WebView:
Kotlin
override fun onDestroy() { myWebView?.let { // Remove the WebView from its parent ViewGroup. (it.parent as? ViewGroup)?.removeView(it) // Stop active loading and clear history. it.stopLoading() it.clearHistory() // Destroy the instance. it.destroy() } myWebView = null super.onDestroy() }
Java
@Override
protected void onDestroy() {
if (myWebView != null) {
// Remove the WebView from its parent ViewGroup.
if (myWebView.getParent() instanceof ViewGroup) {
((ViewGroup) myWebView.getParent()).removeView(myWebView);
}
// Stop active loading and clear history.
myWebView.stopLoading();
myWebView.clearHistory();
// Destroy the instance.
myWebView.destroy();
}
myWebView = null;
super.onDestroy();
}
Entender a memória pós-destruição
Quando você chama destroy(), o sistema libera o contexto Activity, limpa as hierarquias de visualização e interrompe o trabalho em segundo plano da Web. No entanto, você pode observar que a memória física do processo (tamanho do conjunto residente) não cai imediatamente para a linha de base pré-WebView.
Isso é normal. Os caches de execução nativa, as bibliotecas compartilhadas e as páginas de memória alocadas permanecem residentes no processo até que o sistema operacional as recupere ou o processo seja encerrado. O objetivo principal de destroy() é evitar vazamentos cumulativos de memória Activity quando os usuários navegam pelas telas da Web.
Principais métricas de depuração
Ao analisar o consumo de memória do WebView, concentre-se nas seguintes métricas:
Tamanho do conjunto residente (RSS) : a RAM física total mapeada no processo, incluindo código e bibliotecas compartilhados (rotulados como Total no Android Studio Profiler).
RSS anônimo (RssAnon) : memória alocada diretamente pelo processo que não é apoiada por um arquivo no disco (como alocações de heap nativo e de execução do JavaScript). Isso representa o custo de memória principal do conteúdo da Web (rotulado como Alocado no Android Studio Profiler).
Consumo de memória privada (PMF) : a soma do RSS anônimo e da troca (zRAM). O PMF reflete o consumo de memória não removível real que o app impõe ao sistema.
PMF do navegador x PMF do renderizador:memória usada pelo processo principal do app x memória usada pelo processo de renderização isolado. O conteúdo da Web pesado causa picos principalmente no processo de renderização.
Contagens de objetos ativos (
WebViews,Activities,Views): O número de instâncias ativas de interface, contexto eWebViewmantidas na memória. O rastreamento delas identifica se o crescimento da memória é causado por referências Java retidas ou alocações somente nativas.Outros privados e heap nativo:em
dumpsys meminfo, as alocações nativas de C/C++ e os mapeamentos de memória personalizados (como oPartitionAllocdo Chromium ou heaps de execução do JavaScript incorporados) aparecem em Native Heap e Private Other, em vez de Java Heap.
Para mais informações sobre contadores de memória de processos e categorias, consulte o glossário de memória de processos.
Fluxos de trabalho de diagnóstico práticos
Como o WebView opera em vários processos e aloca memória nativa, use as ferramentas e técnicas a seguir para inspecionar o consumo de memória dele:
Ferramentas de criação de perfil e diagnóstico
Para inspecionar alocações de memória e diagnosticar vazamentos, use as seguintes ferramentas:
Memory Profiler do Android Studio: use o Memory Profiler para visualizar alocações nativas, rastrear categorias de memória ao longo do tempo e detectar
Activityem transições de tela.Rastreamento de memória com o Perfetto: use Perfetto para gravar contadores de memória no nível do sistema (como RSS e RSS anônimo) para observar o crescimento geral da memória. Observe que as alocações do mecanismo nativo
WebViewnão produzem pilhas de chamadas na ferramenta de criação de perfil de heap do Perfetto. Use Chrome DevTools para inspecionar snapshots de heap JavaScript e alocações de DOM no conteúdo da Web.
Inspecionar contagens de objetos ativos
Para determinar se o crescimento da memória é causado por objetos de framework Java retidos (como componentes de interface) ou alocações nativas, inspecione a Objects
seção de dumpsys meminfo:
adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"A saída mostra contagens de objetos ativos:
Objects
Views: 142 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 12 AssetManagers: 0
Local Binders: 32 Proxy Binders: 45
Parcel memory: 15 Parcel count: 30
Death Recipients: 2 WebViews: 1
Essa seção mostra contagens de objetos de framework ativos, identificadores de IPC e alocações de Parcel. Para diagnósticos WebView, concentre-se principalmente em Activities
e WebViews.
Execute a interação do usuário de destino (como abrir e fechar uma tela da Web) repetidamente e compare as contagens:
Vazamento de instância:se
WebViewsouActivitiesaumentar em cada navegação e não retornar à linha de base, o app estará vazando a instânciaWebViewJava ou aActivitydo host (por exemplo, devido a umViewGroup.removeView()ausente ou referências de listener retidas). Como umaActivityvazada fixa toda a árvore de visualização e os recursos de imagem decodificados na memória, visitas repetidas vão esgotar rapidamente o heap Java e causar falhasOutOfMemoryError.Vazamento nativo ou de DOM:se
WebViewseActivitiespermanecerem constantes enquanto o RSS total do processo e Outros privados continuarem aumentando, o vazamento terá origem em recursos nativos não liberados, elementos DOM ou vinculações do mecanismo JavaScript. Como essas alocações residem na memória nativa e ignoram o coletor de lixo ART, elas permanecem invisíveis para as ferramentas padrão de detecção de vazamento Java e continuam se acumulando até que o sistema operacional encerre o app.
Criar perfil do processo de renderização isolado usando a CLI
A execução de dumpsys meminfo com o nome do pacote do app só gera memória para o processo do host principal. Para inspecionar o processo de renderização isolado em que as páginas da Web são renderizadas:
Encontre o ID do processo (PID) do serviço de renderização isolado:
adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"A saída mostra o registro do processo isolado e o PID RENDERER_PID (por exemplo,
22155):Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}Inspecione a divisão de memória do processo de renderização usando o PID dele:
adb shell dumpsys meminfo <var>RENDERER_PID</var>Inspecione o processo do app host para avaliar o consumo de memória do navegador:
adb shell dumpsys meminfo <var>PACKAGE_NAME</var>
Inspecionar mapas e alocações de memória
Para saber quais subsistemas ou alocadores nativos ocupam a memória anônima, inspecione os mapas de memória do processo:
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"A tabela a seguir lista tags de memória anônimas comuns e a relevância delas para o crescimento da memória:
| Tag de memória | Subsistema | Relevância para o app e o conteúdo da Web | Causa comum de aumento de memória? |
|---|---|---|---|
[anon:partition_alloc] |
PartitionAlloc do Chromium | Alocações para árvores DOM, buffers de renderização, heap JavaScript V8 e execução do WebAssembly no WebView. |
Sim (alta) : carregar páginas da Web pesadas, DOMs ricos em mídia ou não chamar destroy() em instâncias WebView descartadas inflaciona diretamente essa tag. |
[anon:scudo...] ou [anon:libc_malloc] |
Alocadores de heap nativo do Android (Scudo / jemalloc) | Alocações nativas gerais de C/C++ usadas por bibliotecas NDK, pontes JNI e pipelines gráficos nativos. | Sim (moderado a alto) : o crescimento ocorre quando wrappers JNI nativos ou dependências C++ de terceiros retêm alocações não liberadas em navegações. |
[anon:...] (por exemplo, [anon:quickjs_heap...]) |
Scripts personalizados ou execuções nativas | Mecanismos JavaScript incorporados, execuções personalizadas do WebAssembly ou pools de buffer nativos personalizados. | Sim (dependente do contexto) : comum em apps híbridos que executam mecanismos de script junto com visualizações nativas e não limpam as vinculações de execução. |
Limitações das APIs de memória no app
As APIs de memória no app (como Debug.getMemoryInfo ou
ActivityManager.getProcessMemoryInfo) medem apenas o processo de chamada.
No modo multiprocesso, essas APIs não podem capturar a memória consumida pelo processo de renderização isolado. Para uma avaliação precisa da memória total, confie em ferramentas do sistema como dumpsys meminfo, Perfetto ou Android Studio Profiler.
Triagem de memória alta em um app híbrido
Ao diagnosticar o crescimento inexplicável da memória durante interações recorrentes do WebView (como abrir links da Web ou navegar por feeds da Web), use o fluxo de trabalho de triagem a seguir para isolar se o vazamento tem origem na camada Java ou no mecanismo nativo:
Isolar o tipo de vazamento (Java x nativo): Execute
dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"antes e depois de transições repetidas do usuário (como abrir e fechar artigos da Web ou deslizar pelos feeds).- Observação: se as contagens de
ActivitieseWebViewspermanecerem estáveis (por exemplo, 1 a 2 instâncias ativas), o app não estará vazandoActivitycontextos ou instânciasWebViewJava.
- Observação: se as contagens de
Medir o delta de memória em interações (rastreamento de séries temporais) : capture snapshots de
dumpsys meminfoem várias interações do usuário para calcular a taxa de alocação por transição:- Observação:o heap Java permanece limitado e íntegro (aumentando durante o uso e caindo após a coleta de lixo), mas Outros privados e Heap nativo aumentam constantemente em vários megabytes por transição. Isso prova que o vazamento está inteiramente na memória nativa fora da execução do ART.
Os heap dumps Java padrão (
.hprof) não mostram problemas.
- Observação:o heap Java permanece limitado e íntegro (aumentando durante o uso e caindo após a coleta de lixo), mas Outros privados e Heap nativo aumentam constantemente em vários megabytes por transição. Isso prova que o vazamento está inteiramente na memória nativa fora da execução do ART.
Os heap dumps Java padrão (
Inspecionar mapas de memória anônimos: examine os mapas de memória do processo usando o ADB (consulte Inspecionar mapas e alocações de memória):
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"- Observação:o crescimento da memória está concentrado em
[anon:partition_alloc]ou em heaps de mecanismos de script incorporados, acompanhado de um aumento lento nas referências globais de JNI. Isso indica que, embora as visualizações Java tenham sido substituídas, os objetos de página nativos subjacentes ou as vinculações JavaScript não foram liberados.
- Observação:o crescimento da memória está concentrado em
Correção :
- Verifique se cada
WebViewreciclado ou descartado interrompe explicitamente os scripts ativos (stopLoading()), limpa o histórico e chamadestroy(). - Desfaça os callbacks de ponte JavaScript personalizados ou as referências globais de JNI associadas a visualizações dispensadas.
- Confirme se
Private Othere o RSS do processo se estabilizam após as transições de navegação.
- Verifique se cada
Outros recursos
Para saber mais sobre a depuração e a criação de perfil de memória e desempenho do WebView, consulte os seguintes recursos: