WebView и память

WebView — это мощный компонент, позволяющий отображать веб-контент в вашем Android-приложении. Однако, поскольку это, по сути, полнофункциональный браузерный движок (Chromium), он потребляет значительное количество памяти и имеет сложную многопроцессную архитектуру.

Технические характеристики: Многопроцессная архитектура

На современных устройствах под управлением Android WebView использует многопроцессную модель для повышения безопасности и стабильности. Когда ваше приложение использует WebView, память распределяется между различными процессами:

  1. Процесс браузера (процесс приложения) : это основной процесс вашего приложения. Он содержит объект Java WebView и «браузерную» часть движка Chromium. Этот процесс управляет пользовательским интерфейсом, сетевыми запросами и рендерингом на графическом процессоре (интегрирован непосредственно с конвейером рендеринга Android HWUI). В отличие от Chrome, WebView не имеет отдельного процесса для графического процессора.
  2. Процесс рендеринга : Этот процесс отвечает за анализ HTML, выполнение JavaScript и компоновку. Он изолирован от остальной части системы в целях безопасности. В настоящее время приложения используют только один процесс рендеринга для всех WebView (за исключением нескольких редких особых случаев), в отличие от Chrome, который часто использует отдельные процессы рендеринга для разных сайтов.

Архитектура WebView

Почему это важно для памяти

При использовании dumpsys meminfo <your_package> вы видите только память, используемую процессом браузера (процессом вашего приложения). Память, используемая процессом рендеринга , учитывается отдельно.

Внутри браузерного процесса память WebView распределяется следующим образом:

  • Java-куча : содержит Java-оболочку WebView и связанные с ней объекты.
  • Собственная куча : содержит внутренние структуры данных, кэши и состояние браузерного движка Chromium. Обратите внимание, что из-за использования PartitionAlloc некоторые собственные выделения памяти WebView могут не учитываться в разделе «Собственная куча» в dumpsys meminfo и вместо этого отображаться в разделах «Другое» или «Неизвестно».
  • Разделяемая память : используется для совместного использования графических буферов и других данных. Эта информация может быть нечетко классифицирована командой dumpsys meminfo .

Инструменты для устранения неполадок

Инструменты разработчика Chrome

Наиболее мощным инструментом для анализа памяти внутри WebView (процесса рендеринга) являются инструменты разработчика Chrome.

  1. Включите отладку WebView в вашем приложении:

    // NOTE: In production, this should be gated behind a developer setting
    // or only enabled for debuggable builds to prevent reverse engineering.
    WebView.setWebContentsDebuggingEnabled(true);
    
  2. Подключите устройство через USB.

  3. Откройте Chrome на своем компьютере и перейдите по адресу chrome://inspect/#devices .

  4. Найдите своё приложение и нажмите «Проверить код элемента» .

  5. В окне «Инструменты разработчика» перейдите на вкладку «Память» , чтобы создавать снимки кучи или записывать временные шкалы выделения памяти для кучи JavaScript.

dumpsys meminfo

Используйте команду adb shell dumpsys meminfo --all <package> чтобы увидеть распределение памяти. Найдите в выводе категорию WebView и количество объектов.

Профилирование рендерера

Поскольку рендерер работает в отдельном процессе, вы не можете профилировать его собственную кучу, просто профилируя ваше приложение. Необходимо указать PID процесса рендерера.

Чтобы определить правильный PID рендерера при одновременной активности нескольких WebView:

  1. Используйте dumpsys activity :

    adb shell dumpsys activity processes <your_package_name>
    

    Найдите раздел mConnections . Вы увидите ConnectionRecord , связывающий ваше приложение с SandboxedProcessService . PID этого процесса — это ваш рендерер. Пример:

    mConnections:
      - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}
    
  2. Проверьте имена процессов : процессы рендеринга обычно называются com.google.android.webview:sandboxed_processX или подобным образом. Если WebView используется только одним приложением, скорее всего, он будет только одним.

Получив PID, вы можете выполнить профилирование с помощью heapprofd .

Рекомендации по использованию памяти в WebView

Явное уничтожение

Предполагается, что приложения будут вызывать WebView.destroy() чтобы сообщить о завершении работы с экземпляром.

Хотя WebView пытается обеспечить автоматическую сборку мусора для каждого экземпляра и освобождение всех его ресурсов, гарантировать это на 100% сложно. Даже если автоматическая сборка мусора работает, она может значительно задерживаться, из-за чего приложение будет удерживать ресурсы гораздо дольше, чем ожидалось.

Если приложение вызывает WebView.destroy() в подходящий момент (например, в Activity.onDestroy() ), сохранение ссылки на сам объект WebView не приведет к утечке значительных нативных ресурсов. Нет необходимости обнулять ссылки на объект WebView в полях Activity после его уничтожения, поскольку они будут удалены сборщиком мусора при удалении самой Activity.

Упражнения: практическое применение памяти WebView.

Упражнение 1: анализ многопроцессного следа.

  1. Запустите MemoryLab и выполните базовое измерение объема памяти, используемой вашим приложением:

    adb shell dumpsys meminfo com.android.memorylab
    

    Пример базового уровня (ранго): TOTAL PSS: 18915 KB

  2. Нажмите «Запустить WebView (обычный)» .

  3. В WebView несколько раз нажмите кнопку «Выделить память JavaScript (1000 DIV)» .

  4. Проверьте еще раз память приложения:

    adb shell dumpsys meminfo com.android.memorylab
    
  5. Обратите внимание, что объем памяти в процессе вашего приложения не увеличивается значительно по сравнению с базовым уровнем! Это происходит потому, что элементы DOM находятся в процессе рендеринга .

  6. Найдите процесс рендеринга:

    adb shell ps -A | grep webview | grep sandboxed
    

    Пример выходных данных:

    u0_i9002     14227  1087    1632732 135880 do_epoll_wait       0 S com.google.android.webview:sandboxed_process0
    
  7. Проверьте объем памяти процесса рендеринга (используя его PID):

    adb shell dumpsys meminfo 14227
    
  8. Обратите внимание на высокий общий размер PSS процесса рендеринга. В нашем тестовом запуске он подскочил до ~55 МБ после нескольких выделений памяти. Следует отметить, что выделения памяти JavaScript (обрабатываемые движком V8) обычно попадают в разделы Private Other или Unknown (mmap) файла dumpsys meminfo , а не в кучу Dalvik.

Упражнение 2: утечка памяти в WebView на стороне Java.

Распространенная ошибка — это хранение экземпляра WebView в статическом поле или в долгоживущем объекте, что приводит к утечке памяти. Поскольку объект WebView является «тяжеловесным якорем», который удерживает собственные ресурсы и потенциально целые процессы рендеринга, его утечка памяти обходится очень дорого.

Влияние утечки WebView

  1. В MemoryLab нажмите «Запустить WebView (утечка Java)» .
  2. Активность автоматически закроется после загрузки страницы (имитируя многократную навигацию и накопление утечек).
  3. Нажмите кнопку 4 раза.
  4. Проверьте количество экземпляров WebView в вашем приложении:

    adb shell dumpsys meminfo com.android.memorylab
    

    Внизу страницы найдите раздел «Объекты» . Вы увидите, что количество WebViews увеличилось до 4.

    Пример выходных данных (4 случая утечки) в Rango:

     Objects
               Views:       51         ViewRootImpl:        5
         AppContexts:       14           Activities:        5
              Assets:       38        AssetManagers:        0
       Local Binders:       55        Proxy Binders:       77
       Parcel memory:       41         Parcel count:       68
    Death Recipients:        3             WebViews:        4
    
  5. Сделайте дамп кучи и используйте AHAT для поиска утечки. Если ahat отсутствует в вашем пути, вы можете собрать его из дерева Android:

    # Dump heap from device
    adb shell am dumpheap com.android.memorylab /data/local/tmp/heap.hprof
    adb pull /data/local/tmp/heap.hprof
    # Run ahat using the built JAR (found in out/host/linux-x86/framework/)
    java -jar out/host/linux-x86/framework/ahat.jar -p 8888 heap.hprof
    
  6. В веб-интерфейсе AHAT ( localhost:8888 ) нажмите на ссылку «Выделение памяти» (или «Сайты ») в верхнем меню, чтобы увидеть общее использование памяти.

    Распределение средств AHAT

  7. Найдите класс android.webkit.WebView . Щелкните по количеству его экземпляров , чтобы увидеть все активные экземпляры. В списке должно быть несколько экземпляров.

    Экземпляры AHAT WebView

  8. Щёлкните по одному из утёкших экземпляров WebView . Прокрутите вниз до раздела «Sample Path from GC Root» . Вы увидите, что он хранится в списке sLeakedWebViews в com.android.memorylab.WebViewActivity .

    Путь AHAT к корневому каталогу сборщика мусора


← Нативный | ↑ Вверх | Код приложения →