Оглавление

    Как улучшить производительность во Flutter — приложениях: 9 рекомендаций

    Тема тестирования производительности Flutter приложений (Flutter performance testing) была актуальной, когда фреймворк был еще совсем молодым, и не менее актуальна сейчас.  Производительность – важный аспект любого мобильного приложения. Если оно «съедает» кадры и висит, это оставляет неприятное впечатление у пользователей. Чтобы приложение работало плавно, ему нужно большую часть времени поддерживать кадровую частоту от 60 FPS или, другими словами, менять кадры каждые 16.66 миллисекунд (для поддерживаемых экранов 120 Гц это значение будет 8 мс). Современные сервисы используют множество графических элементов сразу, это сильно увеличивает нагрузку на аппаратную часть устройства. Если усовершенствовать код и архитектуру, можно повысить производительность приложения. 

    Мы уже убедились в этом на практике, так как наша компания стала пионером в области кроссплатформенной разработки на Flutter в 2019 году. А сегодня у нас за плечами уже 25+ успешных проектов в таких отраслях, как финтех, ритейл, медицинские приложения, корпоративные приложения. 

    В этой статье мы расскажем о том, как улучшить производительность 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. Простыми словами, мы просто создаем браузер внутри нашего мобильного приложения, и он отображает пользовательский интерфейс. Вот почему 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 Kotline» или «Flutter vs Swift» (здесь нужно подчеркнуть, что Flutter — это не язык программирования, а фреймворк. И разумнее будет сравнить Dart, язык, используемый во Flutter, со Swift и Kotlin) нативная платформа всегда будет в выигрыше: ее код компилируется в том же формате, что и родной для устройства, и она использует меньше памяти в практически аналогичных приложениях. Однако, благодаря компилятору AOT (ahead of time) и высокооптимизированному движкам рендеринга Skia и Impeller, производительность большинства приложений Flutter обычно находится на одном уровне с нативными, поэтому пользователи не заметят большой разницы.

     Пример из практики Surf: Для одного из наших клиентов мы создали их собственную платформу для потоковой передачи видео, достаточно успешную, чтобы конкурировать с 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 (хронологический график событий)
    • CPU profiler (профилирование ЦП)
    • Rebuild Stats. Содержит список виджетов, повторно используемых в приложении несколько раз. Низкая производительность пользовательского интерфейса в приложениях Flutter обычно вызвана тем, что виджет перерисовывается слишком часто, сотни или даже тысячи раз за несколько секунд. Это указывает на некоторые проблемы с кодом. Rebuild Stats помогает определить, какой виджет вызывает проблемы с производительностью.

    Данные из Performance View  можно экспортировать и импортировать. Мы также рекомендуем ознакомиться с документацией Flutter, чтобы узнать больше о возможностях Performance view.

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

    Данные в 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() нагружает аппаратную часть, и, по возможности, его следует избегать. Виджеты, потенциально способные запустить метод 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 — Flutter справился с анимациями, обеспечил плавную работу приложения и отличный отклик элементов управления воспроизведением. Тем не менее, чтобы свести риск торможений, ошибок или лагов в приложении к минимуму, лучше следовать рекомендациям по улучшению производительности. Надеемся, что наш гайд вам в этом поможет.

    У нас в Surf большой опыт разработки приложений для разных видов бизнеса, и мы с радостью поможем реализовать ваш проект.