Давайте немного отступим от практических основ Flutter и взглянем на внутреннее устройство фреймворка.
В этом параграфе мы заглянем Flutter «под капот». Вы узнаете, из чего состоит Flutter-приложение и на какие слои разбит фреймворк.
Компоненты Flutter-приложения
Для начала посмотрим, из каких компонентов состоит стандартное Flutter-приложение. Чтобы его создать воспользуйтесь командой flutter create.
С первым слоем вы уже знакомы — это содержимое папки lib. Здесь происходит описание работы приложения: вёрстка интерфейса, программирование бизнес-логики, отправка запросов на сервер и так далее. Эту часть нашего приложения называют Dart App.
Второй слой — это как раз фреймворк, то есть Flutter. Посмотреть его исходные коды можно в открытом репозитории на GitHub. Он полностью написан на Dart и содержит в себе всё, что может понадобится разработчику: об этом мы также говорили в предыдущих главах.
Третий слой — движок. В документации Flutter он так и называется — engine. Движок отвечает за рендеринг элементов на экране, платформенные каналы, старт Dart-изолятов и другие функции, про которые мы поговорим чуть позже. Тут важно запомнить, что engine — это не компилятор Dart-кода. Где происходит компиляция мы расскажем чуть ниже.
Четвёртый слой — Embedder API. Это набор методов, который обязана реализовать конкретная платформа, для того чтобы запустить на ней движок Flutter. Это позволяет не привязывать реализацию движка к конкретной платформе.
Набор сущностей, реализующий этот контракт называется embedder — пятый слой нашего приложения. embedder привязывается к операционной системе конкретной платформы, это своего рода адаптер API-системы к API-engine. Наружу, через публичные методы, открываются различные платформенные возможности: обработка событий операционной системы, рендеринг, старт потоков и другие.
👉 Внимательный читатель заметит, что ответственность за рендеринг лежит и на
engine, и наembedder. Ключевая разница здесь заключается в том, чтоengineсодержит логику рендеринга, аembedder— предоставляет возможность рендеринга на платформе.
Эти слои связываются в единое приложение внутри Runner. В нём все части объединяются воедино в пакет приложения, который можно запустить на целевой платформе. Для этого приложение компилируется с помощью инструментов flutter_tools.
Структуру Flutter-приложения можно представить следующей схемой:
Теперь рассмотрим подробнее слои:
- Framework.
- Engine.
- Embedder.
Каждый из них также состоит из компонентов (подробнее можно увидеть на иллюстрации ниже) — расскажем, зачем они нужны, и какие задачи решают.
Начнём со слоя Framework.
Слой: Framework
Его основные компоненты — UI-библиотеки, виджеты, Rendering и Foundation.
UI-библиотеки Material и Cupertino
Они содержат различные готовые элементы, которые сделаны в соответствии с гайдлайнами Android (Material Design) и iOS (Apple HIG). В них входят различные свойственные конкретным платформам UI-элементы.
- Для Material это MaterialButton, AppBar, Scaffold, GridView, MaterialApp и так далее.
- Для Cupertino — CupertinoTabScaffold, CupertinoListTile, CupertinoSwitch, CupertinoApp и другие.
Widgets
С виджетами вы уже познакомились ранее. Подробнее на них мы останавливаться не будем, просто акцентируем ваше внимание, что они — один из компонентов фреймворка.
Rendering
Этот компонент отвечает за:
- различные математические вычисления;
- сетку координат;
- нажатия и их связку с конкретным виджетом;
- подсчёт размеров элементов;
- вычисление скорости скролла и многое другое.
Как мы уже упоминали ранее, основным элементом слоя Rendering является RenderObject. Дерево этих объектов передаётся слою Engine для отрисовки.
👉 Важно: слой Rendering не содержит логики построения этого дерева — оно строится на стороне библиотеки Widgets.
И последнее. Слой Rendering — это абстракция над библиотекой Dart под названием dart:ui.
Она открывает низкоуровневые интерфейсы, которые нужны Flutter для построения приложений: например, классы для обработки ввода или рендеринга.
На самом деле вы можете написать Flutter-приложение без помощи RenderObject, или виджетов или UI-библиотек Material и Cupertino. dart:ui содержит всё необходимое для отрисовки интерфейса простого приложения (Canvas, TextBox, Paint).
Но тогда вам придётся самому просчитывать всю математику, необходимую для отрисовки, отлавливать и обрабатывать пользовательский ввод и, наконец, самостоятельно рисовать интерфейс.
Может быть такой подход и подойдет для очень простых приложений, но как только вам понадобится более сложный интерфейс, вы упретесь в высокую стену. Поэтому пользуйтесь Rendering.
Прочие компоненты
А еще фреймворк содержит следующие компоненты:
- Gestures — классы для работы с пользовательским вводом (нажатия, скролл, различные жесты и так далее).
- Animations — классы для создания собственных анимаций, если вам не хватило функциональности виджетов из Widgets и Material/Cupertino
- Painting — набор классов, которые оборачивают интерфейс отрисовки
engineв более удобное API, умеют работать с тенями, изображениями, закруглениями и прочим - Foundation — низкоуровневые классы-утилиты, которые используются другими слоями фреймворка. А ещё тут происходит самое важное — связывание приложения с
engine. О этом мы подробнее расскажем в одном из следующих параграфов.
Слой: Engine
Давайте теперь поговорим про Engine. Общий, платформенно-не-специфичный engine написан на языках C/C++, он реализует в себе фунционал главных API Flutter.
Задачи, которыми занимается engine, можно представить следующей схемой:
Для рендеринга engine использует графическую библиотеку Skia, разработанную Google. Ей, в том числе, пользуется браузер Google Chrome, система Android и другие продукты. А ещё она — open-source.
Слой: Embedder
К платформенно-специфичным частям engine относятся так называемые embedders. Это shell-оболочки, которые реализуют функционал, специфичный для конкретной платформы. Они выполняют связующую роль между платформенно-не-специфичным Engine и конкретной операционной системой: благодаря этому engine не нужно знать, на какой платформе он сейчас запущен.
В слое embedders происходит взаимодействие с Event Loop — механизмом, который отвечает за обработку событий и асинхронность. Вот как оно устроено:
- Происходит событие — например, пользователь нажал на кнопку, сервер отправил HTTP-ответ и так далее.
embedderперехватывает событие и доставляет егоengine.engineоткладывает операцию ScheduleFrame.- Она планирует задачу перерисовки экрана, путем добавления события в
Event Loop.
Еще embedders содержит логику запуска task runners, необходимых для работы Flutter:
Platform task runner— тут происходит взаимодействие конкретной платформы с движкомengine: перехват различных событий платформы, нативные App Lifecycle и так далее. Он запускается на основном потоке в платформе.UI task runner— тут происходит часть пайплайна рендеринга Flutter,Event Loop, главного изолята.Raster task runner— непосредственно отрисовка на устройстве. Пересборка виджетов и рендеринг на GPU в одном потоке сильно снижает производительность, поэтому рендеринг на устройстве происходит в отдельном потоке.IO task runner— загрузка из памяти/сети различных файлов: большие картинки, аудио, бинарные файлы и другие тяжелые операции.
Embedder сам решает, как распределить task runners по потокам в системе. В общем случае они распределяются по разным потокам, для лучшей производительности, но это не всегда так.
Вот мы и заглянули в «начинку» Flutter. Надеемся, нам удалось рассказать, какой слой и какой компонент за что отвечает.
Чтобы закрепить знания, советуем пройти квиз. А в следующем параграфе мы поговорим о том, как Flutter собирает приложение — и что нужно сделать для его установки на различных платформах.
