Как работает система рендеринга во Flutter
Что проверяют
Понимаете ли вы, что Flutter не превращает виджеты в нативные компоненты, а рисует всё сам, и как устроены три его дерева. Следом почти всегда спрашивают, почему build() можно вызывать часто и что на самом деле дорого. Фронтендеру полезна аналогия: Widget похож на React-элемент, Element на fiber, но браузера и DOM под ними нет.
Как отвечать
Каркас строится вокруг трёх деревьев:
- Widget: неизменяемое описание конфигурации. Создаётся заново при каждом
build()и стоит дёшево; - Element: живой экземпляр виджета в дереве, держит
Stateи связь с родителем. При перестроении сравнивает новый виджет со старым поruntimeTypeиkeyи при совпадении просто обновляет конфигурацию; - RenderObject: делает тяжёлую работу, раскладку и отрисовку. Переживает перестроения, пока Element его не заменит.
Дальше кадр по фазам. setState() помечает Element грязным, и на ближайший vsync фреймворк перестраивает только помеченные поддеревья. Потом layout: ограничения (BoxConstraints) идут сверху вниз, размеры поднимаются снизу вверх, позицию ребёнку назначает родитель. Затем paint записывает команды рисования в слои, compositing собирает дерево слоёв и отдаёт его движку. Растеризация идёт в отдельном raster-потоке, пока UI-поток готовит следующий кадр.
Про движок говорите актуально: Flutter перешёл со Skia на Impeller, который готовит шейдеры заранее и убирает рывки при первом показе анимации. На iOS Impeller единственный вариант, на Android он по умолчанию на современных устройствах, а на старых возможен откат на Skia. В вебе свои рендереры: CanvasKit и skwasm.
Где спотыкаются
Говорят, что при setState() перерисовывается весь экран. Перестраивается поддерево от помеченного виджета, а раскладка и отрисовка пересчитываются только там, где что-то изменилось.
Считают, что частый build() сам по себе тормозит. Тормозит тяжёлая работа внутри него: разбор JSON, сортировка, создание контроллеров.
Не знают про RepaintBoundary и const-конструкторы. Первый выносит часто перерисовываемую часть в свой слой, вторые позволяют фреймворку не перестраивать неизменное поддерево вовсе.