Давайте немного отступим от практических основ 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 собирает приложение — и что нужно сделать для его установки на различных платформах.