Собрать APK на своей машине умеет каждый. На собесе middle и senior проверяют другое: понимаете ли вы режимы компиляции, как развести окружения, как подписывается приложение и как релиз попадает в магазин без ручной работы.
Режимы сборки
| Режим | Компиляция | Для чего |
|---|---|---|
| debug | JIT, ассерты, отладчик, hot reload | разработка |
| profile | AOT, почти как релиз, но с инструментами профилирования | замеры производительности |
| release | AOT, без ассертов и отладки, минимальный размер | пользователи |
Hot reload подменяет код в работающей Dart VM и перестраивает дерево, сохраняя состояние. Он не перезапускает main и initState, поэтому изменения в них или в глобальных инициализаторах требуют hot restart (перезапуск Dart-кода с потерей состояния). Изменения в нативном коде и в pubspec.yaml с плагинами требуют полной пересборки. В release hot reload нет: код скомпилирован AOT в машинный.
Окружения: dart-define и flavors
Адрес API, ключи аналитики и флаги разводят по окружениям, а не правят перед сборкой руками.
flutter run --dart-define=API_URL=https://dev.api.example
flutter build appbundle --dart-define-from-file=env/prod.json
const apiUrl = String.fromEnvironment('API_URL', defaultValue: 'http://localhost');
Значения подставляются при компиляции. Это не способ спрятать секрет: строки легко достать из бинарника. Настоящие секреты живут на сервере.
Flavors создают разные варианты приложения: свой applicationId/bundle id, имя, иконка, Firebase-конфиг. На Android это productFlavors в Gradle, на iOS схемы и конфигурации Xcode. Запуск: flutter run --flavor dev -t lib/main_dev.dart. Так dev и prod сборки стоят на одном телефоне рядом.
Версия
В pubspec.yaml строка version: 1.4.0+27: до плюса видимая версия (versionName, CFBundleShortVersionString), после плюса номер сборки (versionCode, CFBundleVersion). Номер сборки должен расти с каждой загрузкой в магазин, иначе стор её отклонит.
Android
- В Google Play загружают App Bundle (
flutter build appbundle), а не APK: магазин сам собирает APK под архитектуру и плотность экрана устройства. - Подпись: свой ключ загрузки (upload key) в keystore, настройки в
key.properties, который не коммитят. С Play App Signing Google хранит ключ подписи приложения, а потерянный ключ загрузки можно сбросить через поддержку. - R8 сжимает и обфусцирует нативную часть. Если плагин использует рефлексию, могут понадобиться правила ProGuard.
iOS
- Нужен Mac с Xcode, аккаунт Apple Developer, сертификат и provisioning profile.
flutter build ipaсобирает архив для загрузки в App Store Connect, оттуда сборка идёт в TestFlight и на ревью.- Права (камера, геолокация) требуют описаний в
Info.plist, иначе приложение упадёт при запросе или не пройдёт ревью.
Обфускация и символы
flutter build apk --obfuscate --split-debug-info=build/symbols переименовывает символы Dart и выносит отладочную информацию в отдельные файлы. Эти файлы нужно сохранить для каждой версии: без них стек-трейсы из Crashlytics или Sentry не расшифровать (flutter symbolize).
CI/CD
Типичный конвейер: анализатор (flutter analyze), тесты, сборка с номером из CI, подпись секретами из хранилища CI, выгрузка в Firebase App Distribution или TestFlight, затем в стор. Инструменты: GitHub Actions, GitLab CI, Codemagic, fastlane для загрузки в магазины.
Обновлять Dart-код в обход стора, как OTA в React Native, во Flutter штатно нельзя: AOT-код. Есть сторонние решения (например, Shorebird), но и они должны соблюдать правила магазинов.
Что спросят на собесе
- Debug, profile, release: чем отличаются и почему нельзя мерить в debug.
- Hot reload против hot restart: что не подхватит hot reload.
- Как развести dev и prod:
dart-define, flavors. - Как подписывается Android-приложение, зачем App Bundle.
- Зачем
--split-debug-infoи что будет, если потерять символы.