Привет! Меня зовут Нияз, и в этом посте я расскажу о Flutter DevTools — очень богатом и разнообразном наборе инструментов для оценки Flutter-приложений, причем доступном прямо через браузер. Статья представляет собой обзор доступного функционала Flutter DevTools, где я постараюсь описать, что можно сделать с его помощью. Для иллюстраций я по возможности буду использовать приложение «Росбанк Инвест». В ряде случаев нужных примеров в приложении не нашлось, поэтому я придумал их сам. Надеюсь, эта статья будет вам полезна и добавит в ваш инструментарий кое-что новое.

Flutter Inspector

Первый инструмент, который предоставляет Flutter DevTools, — это Flutter Inspector для анализа построения UI. Он помогает определить, насколько качественно строится ваш UI, увидеть широко распространенные проблемы построения интерфейса.

Layout Explorer

Layout Explorer позволяет увидеть все виджеты, отображаемые на экране, — всё дерево виджетов, которое не получится охватить взглядом, просто просматривая код. Если же вы не хотите искать конкретный виджет в огромном дереве, то можно включить Select Widget Mode. Он будет открывать дерево виджета при клике по нему на экране устройства. Этот инструмент будет очень полезен, чтобы ориентироваться в незнакомом коде.

Guidelines

Инструмент Guidelines очерчивает все виджеты на экране. Вот пример, где это может пригодиться.

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

Включаем Guidelines и видим, что средняя картинка имеет внутри какой-то отступ. Значит, надо найти картинку без лишнего отступа: например, попросить дизайнера этот отступ убрать. В нижней части мы видим уже небольшое синее поле — это паддинг, который задан через виджет Flutter. В этом случае надо посмотреть, откуда взялся паддинг в коде, и убрать его.

По цвету заливки можно определить, что Guidelines имеет в виду:

  • Building blocks — голубая рамка;

  • Alignment — желтые стрелки;

  • Scroll — зеленые стрелки;

  • Padding — голубая заливка;

  • Clipping — пунктир с ножницами;

  • Spacers — серая заливка.

Text Baselines

Text Baselines отображает базовую линию, по которой рисуется текст. Полезно, когда возникают сомнения, что текст правильно центрирован. 

Highlight Repaints

Этот инструмент позволяет в реальном времени увидеть границы перерисовки виджетов при работе приложения.

В примере выше код написан так, что вместо маленького загрузчика у нас перерисовывается весь экран. Это не оптимально с точки зрения ресурсов, и благодаря Highlight Repaints мы можем это увидеть. Если всё сделать верно, Highlight Repaints отобразит такую картинку:

В итоге этот инструмент позволит оценить, насколько оптимально написан код анимированного виджета. 

Highlight Oversized Images

Этот инструмент показывает, какие картинки занимают слишком много оперативной памяти с учетом их места на экране. Предположим, мы загрузили картинку 500 на 500 пикселей в память, а на экране она у вас занимает площадь 20 на 20 пикселей. Подобные картинки будут отображены в инвертированных цветах и перевернутыми, в режиме «Highlight Oversized Images».

image6.png

Анализ приложения «Росбанк Инвест» показал, что некоторые иконки инструментов занимают слишком много места. Визуально они сразу выделяются. К этой проблеме мы еще вернемся.

Flutter Performance

Перейдем к анализу скорости отрисовки UI. По стандартам Flutter каждый кадр должен занимать ~16 мс (60 кадров в секунду).

image10.png

Это анализ скорости приложения «Росбанк Инвест» при переключении с вкладки портфеля на вкладку новостей. Мы видим, что здесь всё далеко от идеала. У каждого кадра две колонки. Левая показывает, сколько времени кадр занял у UI-потока, то есть как долго обрабатывался код на Dart. Правая показывает время у растрового потока, за который отвечает Flutter engine, где в основном рисует Skia.

Давайте в качестве примера возьмем первый кадр приложения, попробуем проанализировать его и немного улучшить:

Этот первый кадр отнял много времени у UI потока. Весьма вероятно, что там используется неоптимальный код. Чтобы отследить, сколько времени рисуется тот или иной виджет, включим опцию Track Widget Builds:

image34.png

В ответ получаем вот такую перевернутую пирамидку, где видим, сколько времени занимает build каждого виджета. Весь экран был готов за 10 мс.

image1.png

Приближаем область, где может быть проблема:

image23.png

Виджет FilterChips рисуется несоразмерно долго, вдвое дольше, чем все виджеты внутри него. Он отвечает за вкладки сверху — «портфель», «закладки» и т. п. Я специально обвел его в NiyazNamedWidget, чтобы убедиться, что не ошибся с выводом.

image4.png

Посмотрим код этого виджета. Для отображения виджета предварительно высчитывается размер текста. Этот этап можно убрать, если использовать параметр mainAxisSize, как показано далее. 

Визуально ничего не изменилось:

image14.png

А время построения виджета при этом уменьшилось вдвое, до 1,3 мс:

image13.png

Чтобы приблизить наше приложение к идеалу, 16 мс на кадр, нужно проделать много работы, которая была бы достойна другой статьи. Пока что я остановлюсь на исправлении одного виджета. Чтобы ускорить build этого виджета, я просто использовал Column так, как и «надо» использовать. Проблему можно было бы так же успешно найти методом «пристального взгляда» в код. Однако чтобы проанализировать весь код приложения таким образом, надо потратить много времени. Этот инструмент же наглядно показывает какой виджет самый «медленный». 

CPU Profiler View

CPU Profiler View — это инструмент, который позволяет провести общий анализ того, сколько времени занимает та или иная функция, нагружающая процессор. Эти данные CPU Profiler View начинает записывать после нажатия на кнопку «start recording» и заканчивает после повторного нажатия. Затем он предоставляет отчет о затраченном времени в трех видах: сверху вниз, снизу вверх и перевернутую пирамиду.

image26.png

Я записал лог процессора во время записи, где я просто открыл главную страницу и переключал вкладки. Будем использовать вид «снизу вверх», чтобы посмотреть, сколько каждая отдельная функция заняла у процессора.

image35.png

8% процессорного времени забрала функция, которая создает тему приложения — светлую или темную. Я предположил, что это очень большая проблема. Возможны два сценария: либо эта функция вызывается очень много раз, либо она производит какие-то тяжелые вычисления.

Проверим первую теорию. Я использовал самый мощный инструмент разработчика, который существовал и без Flutter DevTools — print в консоль. В итоге за время переключения вкладок эта функция вызывалась 1650 раз! 

image24.png

Изучив проблему, я обнаружил, что в корне приложения забыли прописать стандартную тему, и поэтому каждый раз при создании какого-либо текста или цвета функция вызывалась заново.

image20.png

Я добавил одну строчку кода, снова записал загрузку процессорного времени, отсортировал отчет по алфавиту и не нашел в нем вызов функции создания темы — ни AppExtendedThemeData.fromContext, ни TextStyle.copyWith. Значит, функция теперь занимает меньше 0,3% всего процессорного времени. В общем итоге я оптимизировал приложение на 8%.

image21.png

Мы разобрали два примера, которые наглядно показывают, что нового дают Flutter DevTools.

Я смог найти «медленный» виджет и починить его. Да, внимательный разработчик мог бы заметить это сам, просто взглянув в код, но времени мы потеряли бы больше. К тому же я узнал и насколько ускорилось построение виджета после оптимизации.

Также я смог найти проблему, которая хоть и отнимала 8% процессорного времени, но была «размазана» по всему времени работы приложения. Эта мелкая и глупая ошибка приводила к огромным потерям, но разработчики не замечали её с самого начала проекта (я проверил историю коммитов файла).

Memory View

Memory View показывает, сколько памяти используется Dart-кодом приложения. Его стоит использовать, когда:

  • приложение перестает работать из-за нехватки памяти;

  • приложение замедляется;

  • приложение перестает отвечать на нажатия;

  • приложение завершает работу из-за превышения лимита памяти, установленного операционной системой;

  • превышен лимит использования памяти, установленный целевым устройством;

  • вы подозреваете утечку памяти.

Вот график использования памяти приложением «Росбанк Инвест»:

image27.png

График использования памяти приложением выглядит скучновато. Я не нашел каких-либо аномалий, которые можно рассмотреть поближе. Так что я вспомнил про oversized-картинки, которые обнаружил ранее по ходу поста. Давайте оптимизируем использование памяти картинками и сравним «до» и «после».

Вот использование памяти объектами класса Image на одном из экранов приложения:

image3.png

А это скриншот после оптимизации:

image16.png

На экране в тот момент было две картинки, размер которых можно было оптимизировать. Разница в 11 КБ на две картинки довольно существенна. Также отмечу, что класс с названием Image встречается в разных пакетах, поэтому стоит обращать внимание на название в правом нижнем углу, чтобы не перепутать.

На скриншоте видно, сколько объектов того или иного класса находится в памяти, сколько они включают объектов внутри себя, сколько весят сами по себе (Shallow Dart Size) и сколько — вместе с содержимым (Retained Dart Size).

Теперь вернёмся к оптимизации картинок. Покажу, как именно я оптимизировал отрисовку картинок. Flutter позволяет нам из коробки указать, какой размер картинки мы хотим кэшировать. Стоит учесть, что Flutter работает с логическими пикселями, а memCacheWidth/memCacheHeight принимает физические пиксели. Вот пример кода для вычисления этих параметров. По сути, всё очень просто: если нам известен размер картинки, то мы умножаем его на плотность пикселей устройства. Плотность пикселей можно узнать из MediaQuery.

image17.png
image25.png

По использованию памяти «Росбанк Инвест» в итоге тоже показал себя хорошо. На десерт расскажу о других инструментах Flutter DevTools, которые стоят упоминания, хоть и не показались столь же интересными для меня.

Анализатор размера приложения

Вот пример анализа APK-файла «Росбанк Инвест» под ARM64:

image11.png

Видим, что очень много занимают ассеты — картинки, шрифты и подобное. Здесь в нашем случае едва ли что-то можно оптимизировать.

Логи приложения

Этот инструмент предлагает чуть более богатые логи, чем те, что идут в терминале. Здесь также содержатся данные о сборе мусора, ивенты Flutter (например, фреймы), stdout и stderr из приложения, кастомные сообщения из приложения.

image8.png

Логи сетевых запросов

Здесь мы можем увидеть все сетевые запросы, которые делает приложение, посмотреть ответы на них. Очень удобно, если вы не хотите переделывать логирование, добавлять какие-то принты, чтобы увидеть это в терминале. Можно одним нажатием включить этот инструмент во время исполнения кода и увидеть историю сетевых запросов.

image15.png

Дебаггер

Во Flutter DevTools предусмотрен даже дебаггер. Это стало для меня неожиданностью, я всегда пользовался дебаггером самой IDE. А теперь я знаю, как можно кодить в блокноте, форматировать консольными командами в терминале, а дебажить в браузере. Вот такие вот страшные вещи можно творить.

image12.png

Слабо представляю, где это реально может понадобиться. Разве что вы хотите запустить код приложения и не хотите ставить на текущее устройство IDE. Но Flutter в любом случае придется качать.

Вывод

Перечислю ситуации, где Flutter DevTools будут крайне полезными:

  • При знакомстве с новой кодовой базой с помощью Layout Explorer можно легко ориентироваться в дереве виджетов.

  • Flutter DevTools позволяет измерить эффективность работы Dart-кода.

  • Будет также полезно оценить работу приложения при помощи Flutter Inspector и CPU Profiler, даже если это ваш собственный код и вы в нем уверены. Сможете открыть много нового.

  • Flutter DevTools содержит инструменты, которые хоть и не дают концептуально новых возможностей, но могут облегчить вашу работу — такие как, например, логи сетевых запросов или дебаггер, которые легко подключить и использовать во время исполнения кода.

Комментарии (1)


  1. PackRuble
    08.08.2023 14:57
    +1

    Спасибо, это было интересное, хотя и короткое путешествие в мир оптимизаций. Как говорится, пишите ещО :)