Как улучшить производительность во Flutter-приложениях: 10 рекомендаций
Тема тестирования производительности Flutter приложений (Flutter performance testing) актуальна для любой компании, которая дорожит своей аудиторией. Исследования показывают, что 53% пользователей скорее всего удалят тормозящее или забагованное приложение, а 80% готовы дать проблемному продукту не более трёх шансов.
Если приложение «съедает» кадры и висит, это оставляет неприятное впечатление у пользователей. Чтобы оно работало плавно, ему нужно большую часть времени поддерживать кадровую частоту от 60 FPS или, другими словами, менять кадры каждые 16.66 миллисекунд (для экранов с поддержкой 120 Гц это значение будет равно 8 мс). Современные сервисы используют множество графических элементов сразу, что сильно увеличивает нагрузку на аппаратную часть устройства. Если усовершенствовать код и архитектуру, можно повысить производительность приложения.
Мы уже убедились в этом на практике, так как стали пионером в области кроссплатформенной разработки на Flutter в 2019 году. А сегодня у нас за плечами уже более 50 успешных проектов в таких отраслях, как финтех, ритейл, медтех и корпоративные приложения.
В этой статье мы расскажем о том, как улучшить производительность Flutter-приложения.
Производительность: Flutter vs другие платформы
Flutter — это кроссплатформенный фреймворк, разработанный компанией Google и использующий язык программирования Dart. В 2021 он стал самой популярной кроссплатформенной технологией на рынке. Протестировать производительность Flutter можно, например, с использованием виджета Performance (run Performance widget for testing). Давайте проведём краткое сравнение производительности Flutter и других технологий для мобильной разработки.
Тестирование производительности Flutter vs React Native
О том, как именно можно протестировать производительность Flutter при помощи виджетов и не только, мы расскажем дальше в статье. В этом разделе остановимся на основном отличии двух фреймворков.
По сравнению с кроссплатформенным фреймворком React Native, Flutter демонстрирует лучшую производительность благодаря своему архитектурному решению доступности компонентов по умолчанию — Flutter не нужен «коммуникационный мост» JavaScript для взаимодействия с нативными компонентами, и у него есть мощные механизмы рендеринга Skia и Impeller. Мост находится на границе фреймворка React Native и пользовательского интерфейса для рисования на графическом движке. Технология довольно медленная и вызывает своего рода замедление.
Другие аспекты сравнения этих технологий вы можете прочитать в статье.
Flutter vs Ionic
Кроссплатформенная технология Ionic использует Web View, системный компонент, отвечающий за открытие веб-страниц в приложениях. Это означает, что для разработки пользовательского интерфейса применяются типичные веб-технологии, такие как HTML и CSS. Простыми словами, программа создает браузер внутри мобильного приложения, и он отображает пользовательский интерфейс. Вот почему mobile UX в приложениях, созданных с помощью Ionic, сильно отличается от приложений, работающих на Flutter или React Native. И низкая производительность — это лишь одна из возможных проблем.
Узнать больше о различиях между двумя технологиями вы можете в статье.
Xamarin vs Flutter
Оба фреймворка обеспечивают почти нативную производительность. Но у Xamarin она сильно зависит от конкретного вида фреймворка. Xamarin.Android и Xamarin.iOS, в которых код требует больше адаптаций под платформу, обеспечивают отличную производительность. У Xamarin.Forms показатели хуже. Кроме того, с Xamarin разработчику может потребоваться разработать многие компоненты пользовательского интерфейса отдельно для iOS и Android, а это означает, что разработка приложений со сложным UI может быть значительно медленнее.
Если рассматривать предыдущие две технологии в разрезе сегодняшнего дня, Xamarin и Ionic потеряли популярность. В то время как Flutter уверенно лидирует среди наиболее часто используемых кроссплатформенных фреймворков.
Flutter vs нативные технологии
Когда речь идёт о нативных платформах (приложения на Swift для iOS и на Kotlin для Android), их производительность, как правило, превосходит любую кроссплатформенную технологию. Причина того, почему в сравнении производительности Flutter vs Kotlin или Flutter vs Swift (а точнее, в сравнении этих нативных языков с Dart) нативная платформа всегда будет в выигрыше: её код компилируется в том же формате, что и родной для устройства, и она использует меньше памяти в практически аналогичных приложениях. Однако благодаря компилятору AOT (ahead of time) и высокооптимизированному движкам рендеринга Skia и Impeller, производительность большинства приложений Flutter обычно находится на одном уровне с нативными, поэтому пользователи не заметят большой разницы.
Пример из нашей практики. Для одного из наших клиентов мы создали их собственную платформу для потоковой передачи видео, достаточно успешную, чтобы конкурировать с YouTube. В этом проекте Flutter хорошо показал себя для разработки высокопроизводительных сервисов: он работает с большим количеством сложных анимаций и обрабатывает рутинную нагрузку в 25 тысяч запросов, а также без проблем справился с нагрузкой в 50 тысяч запросов.
Чтобы узнать, что лучше подходит вашему проекту: Flutter или нативные фреймворки, прочтите нашу специальную статью.
Как измерить производительность Flutter
Для измерения используются средства профилирования и реальное устройство вместо эмулятора (лучше — с низкой производительностью). Тестирование производительности в приложениях Flutter можно проводить несколькими способами, поскольку фреймворк предоставляет широкий спектр возможностей для тестирования и измерения производительности. Ниже мы рассмотрим наиболее популярные из них.
Анализ производительности в оверлее
Один из способов протестировать производительность Flutter — использовать виджет Performance. Наложенный виджет отображает два графика поверх приложения. Верхний график GPU демонстрирует производительность растрового потока, другими словами, передачу информации между деревом слоёв приложения и графическим процессором (GPU) устройства. Несмотря на название, фактически график демонстрирует, как используются ресурсы ЦП. Нижний график UI отображает UI поток, который включает в себя написанный вручную код, выполняемый фреймворком Flutter.
Если один кадр отображается дольше 16,6 миллисекунд (то есть производительность приложения падает ниже 60 FPS), голубая кривая идёт вниз, уступая место белому фону, а вертикальная линия становится красной. Если вы видите такие изменения на графике FPS, это означает, что графические элементы на экране слишком сложные, и приложение не успевает вовремя их отрисовать. Если меняется график UI: Dart-код слишком сложный, и приложение не успевает его вовремя выполнить. Если красная линия появилась на обоих графиках, то стоит проверить код UI.
Анализ производительности в оверлее можно запустить несколькими способами. Вот два из них:
- Flutter inspector. Запустите приложение в режиме профилирования, откройте DevTools и переключите флаг Inspector view. Там вы найдёте кнопку Performance Overlay.
- Командная строка. Используйте команду flutter run —profile, а затем нажмите клавишу P, чтобы включить виджет анализа производительности.
Подробнее о профилировании производительности во Flutter можно почитать в официальной документации.
Performance view
С помощью Performance view, доступного в DevTools, можно узнать производительность приложения. Для этого используют три инструмента.
Flutter frames chart (график кадров) в режиме реального времени показывает информацию о каждом кадре: время рендера, фреймрейт, возникающие ошибки или зависания.
Timeline events chart (хронологический график событий) отслеживает все события, происходящие в приложении: отрисовку сцен, сборку экранов, HTTP-трафик и т.д.
CPU profiler (профилирование ЦП) показывает, сколько времени и ресурсов процессор затрачивает на каждый кадр, а также скорость ответа на запрос trace.
Rebuild Stats содержит список виджетов, повторно используемых в приложении несколько раз. Низкая производительность пользовательского интерфейса в приложениях Flutter обычно вызвана тем, что виджет перерисовывается слишком часто, сотни или даже тысячи раз за несколько секунд. Это указывает на определённые проблемы с кодом. Rebuild Stats помогает понять, какой виджет вызывает проблемы с производительностью.
Данные из Performance view можно экспортировать и импортировать. Мы также рекомендуем ознакомиться с документацией, чтобы узнать больше о возможностях Performance view.
Описанные инструменты довольно сложны, поэтому мы рекомендуем использовать их, когда вы уже проверили остальные аспекты, но все ещё сталкиваетесь с низкой производительностью приложения.
Эталонные тесты
Производительность приложения также можно измерить с помощью эталонных тестов Flutter, которые выполняют посредством интеграционного тестирования. Эти тесты оценивают такие метрики, как время запуска, энергопотребление и количество пропущенных кадров.
Как оптимизировать производительность во Flutter
Старайтесь не перегружать метод build
Тяжёлый циклический метод build() потребляет чрезмерные ресурсы ЦП. Так происходит, когда вы используете крупный Widget с «тяжёлой» функцией build(). Лучше разделить такой виджет на виджеты поменьше, исходя из их внутренней логики и того, как они будут изменяться. К примеру, локализовать вызов setState() до той части поддерева (другими словами, дочерних элементов узла), которой требуются изменения UI. Если setState() вызывается на слишком раннем этапе, он пересобирает все последующие виджеты дерева.
Используйте виджеты const
Постарайтесь применять const виджеты, где это возможно. В таком случае для const виджетов с одинаковой конфигурацией будет создан один объект, который будет переиспользоваться в приложении.
Используйте Opacity только при необходимости
С Opacity виджет пересобирается в каждом кадре, что может вызвать проблемы с производительностью Flutter, особенно если вы используете анимацию. Если параметр Opacity равен или очень близок к нулю — отображать такой виджет не имеет смысла, т.к. он не будет виден пользователю, но framework всё равно будет его отображать, потребляя дополнительные ресурсы. Если применить Opacity непосредственно к изображению, потребляется меньше ресурсов, чем в случае с виджетом. Ещё, чтобы добиться хорошей оптимизации, мы советуем выбирать виджеты TransparentImage, AnimatedOpacity, FadeInImage или FadeInTransition вместо виджета Opacity.
Не применяйте saveLayer
Метод saveLayer() нагружает аппаратную часть, и по возможности его следует избегать. Виджеты, потенциально способные запустить этот метод: Text (если используется overflowShader); Chip (если disabledColorAlpha != 0xff); ColorFilter и ShaderMask. Кроме того, чтобы избежать вызова функции saveLayer(), настройте свойство виджета borderRadius так, чтобы оно скругляло углы прямоугольника, вместо использования ClipRRect.
Сокращайте сборку и рендеринг до 16 мс
Если вы заметили, что приложение «съедает» кадры, посмотрите, какие кадры собираются и рендерятся дольше 16 миллисекунд. Так как для сборки и рендеринга используются отдельные потоки, вам нужно, чтобы каждый кадр собирался и рендерился за 8 миллисекунд или быстрее, чтобы в сумме вышло 16 мс или меньше.
Если кадры рендерятся меньше чем за 16 мс, пользователь особой разницы не заметит, но заряда батареи будет хватать дольше, а само устройство будет меньше греться. Более подробно об эффективных практиках рендеринга можно прочитать в документации по Flutter.
Выбирайте SizedBox вместо Container
Чтобы создать поле или пустую область заданного размера, используйте виджет SizedBox, который отвечает лишь за размеры компонента, в отличие от Container, в котором собраны всевозможные виджеты компоновки, размеров, цвета, формы и т.д. Используйте const SizedBox.shrink() для отображения пустых областей.
Передавайте в AnimatedBuilder уже предсобранные поддеревья в качестве дочерних параметров
Если вы используете виджет AnimatedBuilder, не рекомендуется помещать в функцию билдера дочерний элемент узла, который не зависит от анимации, потому что поддерево будет пересобираться на каждом шаге анимации. Лучше один раз соберите поддерево и передавайте его виджету в качестве дочернего параметра.
Не используйте ListsView для длинных списков
Если список не помещается на экран, создайте его с помощью конструктора ListView.builder, а не ListView() или Column(). Так конструктор будет рендерить пункты по мере того, как пользователь будет их скроллить. Иначе приложение создаст все пункты сразу и снизит производительность.
Скажите «нет» разделению виджетов на методы
Если у вас есть крупный метод генерации с несколькими уровнями вложения, может показаться, что разделить его на несколько методов — отличная идея. Однако в результате Flutter будет пересобирать все дочерние виджеты с каждой пересборкой родительских, даже если некоторые из них абсолютно статичны. Чтобы не расходовать ресурсы ЦП на повторные сборки, разбивайте сложные виджеты на виджеты StalessWidget поменьше.
Подведём итоги
Flutter известен среди разработчиков тем, что обеспечивает почти нативную производительность даже в приложениях со сложными визуальными эффектами. Один из примеров — приложение, которое мы в Surf разработали для видеостриминговой платформы The Hole — фреймворк справился с анимациями, обеспечил плавную работу приложения и отличный отклик элементов управления воспроизведением. Тем не менее, чтобы свести риск торможений, ошибок или лагов в приложении к минимуму, лучше следовать рекомендациям по улучшению частоты кадров. Надеемся, что наш гайд вам в этом поможет.
У нас в Surf большой опыт разработки приложений для разных видов бизнеса, и мы с радостью поможем реализовать ваш проект.