O WebView executa código nativo em vários processos para renderizar conteúdo da Web
no app Android. Deixar as instâncias de WebView sem gerenciamento pode levar a vazamentos de
memória, falhas de falta de memória (OOM, na sigla em inglês) e degradação do desempenho do app.
Neste documento, explicamos o modelo de memória de vários processos do WebView, descrevemos como
gerenciar corretamente o ciclo de vida dele para evitar vazamentos e fornecemos fluxos de trabalho
práticos para diagnosticar problemas de memória.
Entender a arquitetura de memória do WebView
Para gerenciar a memória do WebView de forma eficaz, entenda como o Android aloca
recursos para conteúdo da Web:
Execução de vários processos:no Android 8.0 (nível 26 da API) e versões 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
Activitye o código Java ou Kotlin são executados. - Processo do renderizador isolado:um processo separado em sandbox
(
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
Consumo de memória nativa:a maior parte da memória do
WebView, incluindo gráficos renderizados, a árvore do DOM e a memória de ambiente de execução do JavaScript, é alocada na memória nativa, não no heap Java. Um heap dump do Java (.hprof) mostra apenas um objeto wrapper leve do Java 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
maxHeapdo app e falham rapidamente com umOutOfMemoryError, a memória nativa pode crescer silenciosamente até 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 memória. Isso prejudica a multitarefa geral do dispositivo antes de encerrar o aplicativo 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 remover uma WebView do layout ou permitir que uma
Activity termine libera automaticamente a memória.
Para garantir a limpeza completa das referências de contexto do Java e dos recursos de renderização
nativos, é necessário orquestrar explicitamente uma sequência de encerramento no ciclo de vida do componente
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 encerramento limpo e liberar recursos quando o Activity ou
Fragment for destruído, faça o seguinte:
- Remova o
WebViewdo contêiner pai (ViewGroup). - Interrompe o carregamento ativo e limpa o histórico de navegação.
- Chame o método
destroy(). - Limpe a referência a
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();
}
Noções básicas sobre 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 notar que
a memória física do processo (tamanho do conjunto residente) não cai imediatamente para
o valor de referência pré-WebView.
Isso é normal. Os caches de ambiente de execução nativo, 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 do destroy() é evitar
vazamentos cumulativos de memória do Activity quando os usuários navegam para dentro e para fora de telas
com tecnologia da Web.
Principais métricas de depuração
Ao analisar o consumo de memória do WebView, concentre-se nas seguintes métricas:
Resident Set Size (RSS): o total de RAM física 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 tem um arquivo de backup no disco (como heap nativo e alocações de memória do ambiente de execução do JavaScript). Isso representa o custo de memória principal do conteúdo da Web (rotulado como Allocated no Android Studio Profiler).
Consumo de memória particular (PMF, na sigla em inglês): a soma do RSS anônimo e do espaço de troca (zRAM). A PMF reflete a carga real de memória não removível que o app impõe ao sistema.
PMF do navegador x PMF do renderizador:memória usada pelo processo principal do app em comparação com a memória usada pelo processo do renderizador isolado. O conteúdo pesado da Web causa picos principalmente no processo do renderizador.
Contagens de objetos ativos (
WebViews,Activities,Views): o número de instâncias ativas de interface, contexto eWebViewmantidas na memória. O rastreamento dessas informações identifica se o crescimento da memória é causado por referências Java retidas ou por alocações somente nativas.Private Other e Native Heap:no
dumpsys meminfo, as alocações nativas de C/C++ e os mapeamentos de memória personalizados (como oPartitionAllocdo Chromium ou os heaps de ambiente de execução do JavaScript incorporado) aparecem em Native Heap e Private Other, em vez de Java Heap.
Para mais informações sobre os contadores de memória de processo e as categorias deles, consulte o Glossário de memória de processo.
Fluxos de trabalho de diagnóstico práticos
Como o WebView opera em vários processos e aloca memória nativa, use as seguintes ferramentas e técnicas para inspecionar o consumo:
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, monitorar categorias de memória ao longo do tempo e detectar vazamentos de
Activityem transições de tela.Rastreamento de memória com o Perfetto:use o Perfetto para registrar contadores de memória no nível do sistema (como RSS e RSS anônimo) para observar o crescimento geral da memória. Observação: as alocações de mecanismo nativo do
WebViewnão produzem pilhas de chamadas na ferramenta de perfil de alocação heap do Perfetto. Use o Chrome DevTools para inspecionar instantâneos de alocação heap do 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 retidos do framework Java (como componentes de interface) ou por alocações nativas, inspecione a seção Objects
de dumpsys meminfo:
adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"A saída mostra as 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
Esta seção mostra as contagens de objetos de framework ativos, identificadores de IPC e
alocações de Parcel. Para diagnósticos de WebView, concentre-se principalmente em Activities
e WebViews.
Realize 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 ao valor de referência, o app está vazando a instânciaWebViewdo Java ou o hostActivity(por exemplo, devido a umaViewGroup.removeView()ausente ou a 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 do Java e causar falhas deOutOfMemoryError.Vazamento nativo ou de DOM:se
WebViewseActivitiespermanecerem constantes enquanto o RSS total do processo e Private Other continuarem a subir, o vazamento será originado 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 do ART, elas permanecem invisíveis para as ferramentas padrão de detecção de vazamento do Java e continuam se acumulando até que o sistema operacional encerre o app.
Criar o perfil do processo de renderização isolado usando a CLI
Executar 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 de 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 o detalhamento da memória do processo do renderizador usando o PID:
adb shell dumpsys meminfo <var>RENDERER_PID</var>Inspecione o processo do app host para avaliar o consumo de recursos no navegador:
adb shell dumpsys meminfo <var>PACKAGE_NAME</var>
Inspecionar mapas e alocações de memória
Para ver quais subsistemas ou alocadores nativos ocupam 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 comuns de memória anônima e a relevância delas para o crescimento da memória:
| Tag de memória | Subsistema | Relevância para o conteúdo do app e da Web | Causa comum do aumento de memória? |
|---|---|---|---|
[anon:partition_alloc] |
PartitionAlloc do Chromium | Alocações para árvores do DOM, buffers de renderização, heap de JavaScript V8 e execução do WebAssembly em WebView. |
Sim (alto): o carregamento de páginas da Web pesadas, DOMs com muitos recursos de mídia ou a falha ao chamar destroy() em instâncias WebView descartadas infla diretamente essa tag. |
[anon:scudo...] ou [anon:libc_malloc] |
Alocadores de heap nativos do Android (Scudo / jemalloc) | Alocações nativas gerais de C/C++ usadas por bibliotecas do NDK, pontes JNI e pipelines de 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 várias navegações. |
[anon:...] (por exemplo, [anon:quickjs_heap...]) |
Scripts personalizados ou ambientes de execução nativos | Mecanismos JavaScript incorporados, ambientes de execução personalizados do WebAssembly ou pools de buffer nativos personalizados. | Sim (depende do contexto): comum em apps híbridos que executam mecanismos de script com visualizações nativas e não limpam as vinculações de ambiente 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 de vários processos, 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, use
ferramentas do sistema, como dumpsys meminfo, Perfetto ou Android Studio Profiler.
Como fazer a triagem de memória alta em um app híbrido
Ao diagnosticar um aumento inexplicável de memória durante interações recorrentes com o WebView,
como abrir links da Web ou navegar em feeds baseados na Web, use
o seguinte fluxo de trabalho de triagem para isolar se o vazamento se origina na
camada Java ou no mecanismo nativo:
Isole 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 por feeds).- Observação:se as contagens de
ActivitieseWebViewspermanecerem estáveis (por exemplo, 1 a 2 instâncias ativas), o app não está vazando contextos deActivityou instâncias deWebViewdo Java.
- Observação:se as contagens de
Medir o delta de memória em várias interações (rastreamento de série temporal): 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 do Java permanece limitado e íntegro (com picos durante o uso
e quedas após a coleta de lixo), mas Private Other e
Native Heap aumentam constantemente em vários megabytes por transição. Isso
prova que o vazamento está inteiramente na memória nativa fora do ambiente de execução do ART.
Os heap dumps padrão do Java (
.hprof) não mostram problemas.
- Observação:o heap do Java permanece limitado e íntegro (com picos durante o uso
e quedas após a coleta de lixo), mas Private Other e
Native Heap aumentam constantemente em vários megabytes por transição. Isso
prova que o vazamento está inteiramente na memória nativa fora do ambiente de execução do ART.
Os heap dumps padrão do Java (
Inspecione mapas de memória anônimos:examine os mapas de memória do processo usando o ADB (consulte Inspecionar mapas de memória e alocações):
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"- Observação:o crescimento da memória está concentrado em heaps de
[anon:partition_alloc]ou de mecanismos de script incorporados, acompanhado por 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 ou as vinculações JavaScript não foram liberados.
- Observação:o crescimento da memória está concentrado em heaps de
Remediação:
- Verifique se cada
WebViewreciclado ou descartado interrompe explicitamente os scripts ativos (stopLoading()), limpa o histórico e chamadestroy(). - Remova callbacks de ponte JavaScript personalizados ou referências globais de JNI associadas a visualizações dispensadas.
- Confirme se o
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 como depurar e analisar a memória e o desempenho do WebView,
consulte os seguintes recursos: