Apple HIG). В них входят различные свойственные конкретным платформам UI-элементы.
С виджетами вы уже познакомились в главах 1.3 — 1.5. Подробнее на них мы останавливаться не будем, просто акцентируем ваше внимание, что они — один из компонентов фреймворка.
Этот слой отвечает за:
Как мы уже упоминали в главе 1.3, основным элементом слоя Rendering является RenderObject. Дерево этих объектов передаётся слою Engine для отрисовки.
Важно: слой Rendering не содержит логики построения этого дерева — оно строится на стороне библиотеки Widgets.
И последнее. Слой Rendering — это абстракция над библиотекой Dart под названием dart:ui
.
Она открывает низкоуровневые интерфейсы, которые нужны Flutter для построения приложений: например, классы для обработки ввода или рендеринга.
На самом деле вы можете написать Flutter-приложение без помощи RenderObject, или виджетов или UI-библиотек Material и Cupertino. dart:ui
содержит всё необходимое для отрисовки интерфейса простого приложения (Canvas, TextBox, Paint).
Но тогда вам придётся самому просчитывать всю математику, необходимую для отрисовки, отлавливать и обрабатывать пользовательский ввод и, наконец, самостоятельно рисовать интерфейс.
Может быть такой подход и подойдет для очень простых приложений, но как только вам понадобится более сложный интерфейс, вы упретесь в высокую стену. Поэтому пользуйтесь Rendering.
А еще фреймворк содержит следующие компоненты:
engine
в более удобное API, умеют работать с тенями, изображениями, закруглениями и прочимengine
. О этом мы подробнее расскажем в параграфе 2.3Давайте теперь поговорим про Engine. Общий, платформенно-не-специфичный engine
написан на языках C/C++, он реализует в себе фунционал главных API flutter.
Задачи, которыми занимается engine, можно представить следующей схемой:
Для рендеринга engine
использует графическую open-source библиотеку Skia, разработанную Google. Ей, в том числе, пользуется браузер Google Chrome, система Android и другие продукты.
К платформенно-специфичным частям engine
относятся так называемые embedders
. Это shell-оболочки, которые реализуют функционал, специфичный для конкретной платформы. По сути они выполняют связующую роль между платформенно-не-специфичным Engine и конкретной операционной системой: благодаря этому engine
не нужно знать, на какой платформе он сейчас запущен.
В слое embedders
происходит взаимодействие с eventloop
— механизмом, который отвечает за обработку событий и асинхронность. Вот как оно устроено:
embedder
перехватывает событие и доставляет его engine
.engine
откладывает операцию ScheduleFrame.eventloop
.Еще embedders
содержит логику запуска task runners
, необходимых для работы Flutter:
Platform task runner
— тут происходит взаимодействие конкретной платформы с движком engine
: перехват различных событий платформы, нативные App Lifecycle и так далее. Он запускается на основном потоке в платформе.UI task runner
— тут происходит часть пайплайна рендеринга Flutter, eventloop главного изолята.Raster task runner
— непосредственно отрисовка на устройстве. Пересборка виджетов и рендеринг на GPU в одном потоке сильно снижает производительность, поэтому рендеринг на устройстве происходит в отдельном потоке.IO task runner
— загрузка из памяти/сети различных файлов: большие картинки, аудио, бинарные файлы и другие тяжелые операции.Embedder
сам решает, как распределить task runners
по потокам в системе. В общем случае они распределяются по разным потокам, для лучшей производительности, но это не всегда так.
Теперь когда мы узнали всё необходимое про технологию, которую мы используем как инструмент для разработки и написали свое первое приложение, возникает вопрос: «А как это приложение доставить для наших пользователей»?
До этого времени мы с вами использовали Flutter в так называемом режиме разработки или debug-режиме, но на самом деле это не единственный режим для запуска нашего приложения.
Но прежде чем приступить к разбору различных типов запуска Flutter-приложения давайте с вами поговорим про то, как может компилироваться написанный нами Dart-код
Dart может компилироваться в двух вариантах: Ahead of time и Just in time.
AOT компиляция — это когда наш код собирается до запуска нашего приложения. При AOT-компиляции необходимо пройтись по каждой части программы и скомпилировать ее.
JIT компиляция — когда код может компилироваться прямо в процессе работы нашей программы. При JIT-компиляции нам не обязательно компилировать весь исходный код: мы можем скомпилировать только описание функций и методов. В процессе работы программы, когда исполнение кода дойдет до них, JIT-компилятор скомпилирует их в моменте, если компиляция не производилась ранее.
Принципиальное отличие между JIT и AOT в том, когда и как исходный код компилируется в промежуточное представление, необходимое dartVM.
При разработке приложений, мы как правило, пользуемся JIT-компиляцией кода Dart. Как вы могли догадаться, именно она позволяет нам использовать функционал Hot restart & reload. При внесении изменений в код повторно компилируются только те исходники, которые были нами затронуты. Это очень экономит время на тестировании работы кода и позволяет сфокусироваться на решении задач.
Но такой функционал не даётся нам бесплатно: за удобную и быструю разработку мы платим производительностью нашего приложения. Перекомпиляция в момент работы приложения занимает определённое время и напрямую влияет на производительность программы. При таком раскладе конечный пользователь будет получать просадки кадров и, скорее всего, ему это не очень понравится.
Если же мы компилируем наш Dart-код в AOT-режиме, мы теряем возможность использовать Hot restart & reload, но не тратим ресурсы устройства на компиляцию в процессе исполнения. Пользователи получают более плавную и ровную производительность приложения, быстрый запуск.
Flutter позволяет использовать компиляцию Dart-кода в JIT- и AOT-режиме для разных вариантов сборки и запуска наших приложений. Всего их три — давайте рассмотрим их подробнее.
Это стандартный режим запуска Flutter-приложения. Именно в нём мы запускаем приложение командой flutter run
, если явно не указываем другой тип сборки. Он спроектирован специально для разработки, и не предполагает быстрой производительности или минимально возможного веса приложения. В нём есть возможность пользоваться инструментами разработчика для отладки работы вашего кода, например ставить точки остановки, смотреть стек вызовов в конкретном участке кода, содержимое переменных. В этом режиме dart код собирается JIT компилятором.
Это режим, похожий на Debug mode — за исключением того, что конечный код получается более оптимизированным, приближенным к тому, который получат наши пользователи. Здесь dart runtime
содержит только ту дополнительную информацию, которая нужна для профилирования нашего приложения. Здесь используется AOT-компиляция и не доступен Hot reload & restart. Чтобы собрать приложение в profile-режиме, добавьте флаг --profile
:
flutter run --profile
Обычно profile-сборки собирают, чтобы наблюдать за производительностью приложения. Вы можете воспользоваться инструментом performance view, чтобы посмотреть на стабильность работы вашего приложения.
Используется для того, чтобы собрать финальное приложение, которое попадёт в руки конечных пользователей. В этом режиме проводятся всевозможные оптимизации нашего кода, убирается неиспользуемый код, нет проверок для отлова не-критичных ошибок, отключены assert. Здесь используется AOT-компиляция. Чтобы запустить приложение в release mode добавьте флаг --release
:
flutter run --release
Если вы хотите просто собрать конечный артефакт для конкретной платформы воспользуйтесь командой build
. Вам необходимо указать имя интересующего вас артефакта и режим сборки — --release
. Например, чтобы собрать Android Package Kit (.apk), выполните следующую команду:
flutter build apk --release.
По окончанию сборки, директория, куда попал артефакт будет выведена в консоль. Обычно этоbuild/app/outputs/flutter-apk/app-release.apk
На самом деле когда мы вызываем команду flutter run
, внутри она использует эту же команду flutter build. Вы можете очистить директорию build/app/outputs/flutter-apk
(для android), и затем запустить приложение из IDE или командной строки — собранный артефакт вновь окажется в очищенной директории, даже если вы не вызвали напрямую flutter build
. Учтите, что изменения, которые вы делаете, используя Hot restart & reload не попадают в него.
Если у вас есть MacBook, вы можете попробовать запустить команду flutter build ipa
, чтобы собрать iOS-артефакт.
Чтобы ваше приложение без проблем установилось на устройства, его необходимо подписать: для этого вам нужны ключи и сертификаты.
Подпись приложений это механизм, позволяющий проверить, что обновление приложения, которое вы устанавливаете, приходит из одного и того же источника, и что оно не было подделано.
Представьте, что вы решили установить/обновить приложение Яндекс.Музыки на системе Android из неизвестного источника — а им владеет злоумышленник, не обладающий ключом подписи Яндекса. В этом случае система сообщит вам, что приложение не было создано Яндексом и устанавливать его не рекомендуется
Чтобы создать собственный ключ для Android воспользуйтесь следующей командой
Для Mac:
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
Для Windows:
keytool -genkey -v -keystore %userprofile%\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload
Важно: У вас должна быть установлена Java и keytool (идёт в комплекте с Java).
Придумайте сложный пароль и не потеряйте сгенерированный файл
Если вы ещё не публиковали своё приложение в сторы то потеря ключа не так страшна — можно сгенерировать новый и использовать его. Но стоит иметь в виду, что после публикации поменять ключ, который используется для подписи, достаточно затруднительно.
Далее чтобы собрать Android-приложение, подписанное вашим ключом, вам необходимо в директории Android-проекта создать файл с названием key.properties
. Проверьте, что файл добавлен в .gitignore: в нём будут лежать пароли от сгенерированного ключа.
Добавьте в key.properties
следующее содержимое:
storePassword=<Ваш пароль store password>
keyPassword=<Ваш пароль от ключа>
keyAlias=upload
storeFile=<Полный путь до директории где лежит сгенерированный ключ>
Например
storePassword=my-store-password
keyPassword=my-key-password
keyAlias=my-custom-alias
storeFile=C:/keys/my-first-app-key.jks
Далее откройте файл build.gradle расположенный в [корневая папка проекта]/android/app/build.gradle
и добавьте в него перед началом блока android
следующие строчки:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
...
}
Затем найдите блок buildTypes
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now,
// so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
И замените его на
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
Теперь при сборки вашего приложение в release режиме оно будет подписано сгенерированным вами ключем.
Вы можете сделать отдельные ключи для debug и releasе режимов, или разных окружений приложения, например тестовые сборки вы можете подписывать отдельным ключем.
Если вы хотите узнать как подписать ваше iOS приложение, ознакомьтесь с официальной документацией Apple
Apple использует для подписи так называемые сертификаты, которые сам и выдаёт. Они имеют ограниченный срок службы и их периодически нужно будет обновлять.
Сертификаты используются для подписи iOS-артефактов, всего их существует 4 вида:
Если вы не публикуете свое приложение, то для разработки вам будет достаточно первого (developer). Для него не нужен apple developer account и можно создать его автоматически в Xcode, когда вы попытаетесь собрать из него iOS-версию вашего приложения.
Если же вы решите опубликовать приложение, то для получения остальных сертификатов вам потребуется приобрести Apple Developer Account.
Если у вас возникнет необходимость подписать десктоп-приложение (например перед публикацией в одном из магазинов приложений), то вы можете ознакомиться со следующими материалами:
Web приложения, как правило, не нуждаются в подписи.