Соберите релизный app bundle
Для новых приложений Google Play принимает только Android App Bundle (.aab). Flutter собирает его одной командой, а режим release используется по умолчанию. Сначала проверьте две вещи: обе трудно исправить, когда приложение уже у тестировщиков.
Задайте ID приложения
В
android/app/build.gradle.ktsукажите дляapplicationIdуникальное значение в формате обратного домена, напримерcom.yourcompany.app. После первой загрузки в Google Play его нельзя изменить.Проверьте разрешение INTERNET
Если приложение работает с сетью, в
android/app/src/main/AndroidManifest.xmlдолжно быть объявленоandroid.permission.INTERNET. В манифестах debug и profile оно есть, а в release появится, только если вы его добавите.Соберите бандл
Выполните команду ниже в корне проекта. Flutter сохранит бандл в
build/app/outputs/bundle/release/.
flutter build appbundleИспользуете flavors? Соберите тот, который публикуете, например flutter build appbundle --flavor production, и проверьте, что он использует продакшен-имя пакета и продакшен-ключи API.
Подпись: ключ загрузки и подписание приложений в Google Play
Для новых приложений подписание в Google Play обязательно: ключ подписи приложения хранит Google, а каждую загрузку вы подписываете своим ключом загрузки. Если ключ загрузки потеряется, его можно сбросить через Google; само приложение при этом не теряется. Создайте ключ с помощью keytool:
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \
-storetype JKS -keysize 2048 -validity 10000 -alias uploadЗатем создайте android/key.properties с паролями и путём к хранилищу ключей. Ни этот файл, ни .jks не должны попадать в систему контроля версий:
storePassword=<keystore password>
keyPassword=<key password>
keyAlias=upload
storeFile=/Users/<you>/upload-keystore.jksЗагрузите файл в android/app/build.gradle.kts и привяжите тип сборки release к новой конфигурации подписи:
import java.util.Properties
import java.io.FileInputStream
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
// ...
signingConfigs {
create("release") {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) }
storePassword = keystoreProperties.getProperty("storePassword")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}В старых проектах вместо build.gradle.kts используется build.gradle (Groovy); поля те же. После изменения файлов Gradle выполните flutter clean.
Код версии и целевой уровень API
У каждой загрузки код версии должен быть больше, чем у любого ранее загруженного бандла в любом треке. Во Flutter это число после + в pubspec.yaml:
version: 1.0.0+11.0.0 становится названием версии, которое видят пользователи, а 1 — кодом версии. Увеличивайте его до +2, +3 и так далее или переопределяйте оба значения при сборке через --build-name и --build-number.
С 31 августа 2026 года новые приложения и обновления должны быть нацелены на Android 16 (уровень API 36) или выше. В файле Gradle стоит targetSdk = flutter.targetSdkVersion, поэтому обычно его повышают обновлением Flutter. Если ваша версия Flutter старее, укажите targetSdk = 36 вручную и перед загрузкой протестируйте приложение на свежей версии Android.
Ловушки Flutter, из-за которых затягивается закрытый тест
- Повторный номер сборки. Play Console отклоняет бандл, если его код версии уже использовался. Увеличивайте
+Nвpubspec.yamlперед каждой сборкой, которую собираетесь загрузить. - Обфускация без символов.
--obfuscateработает только вместе с--split-debug-info. Сохраняйте папку с символами каждой загруженной сборки, иначе вы не сможете прочитать отчёты о сбоях от тестировщиков. - Не тот flavor. С flavors легко загрузить staging-сборку со staging-адресами. Тогда тестировщики сообщают об ошибках, которых продакшен-пользователи никогда не увидят.
- Нет сети в release. Если в основном манифесте нет разрешения INTERNET, приложение работает в debug и молча ломается у тестировщиков.
- Бандл с отладочной подписью. Если тип сборки
releaseвсё ещё использует отладочную подпись, Play Console отклонит загрузку. - Плагины со старым нативным кодом. Плагины с библиотеками
.soдолжны поддерживать страницы памяти 16 КБ. Play Console предупреждает об этом, а с 1 февраля 2027 года обновления без такой поддержки нельзя будет выпустить. Обновите плагины и Flutter SDK до теста, а не посреди него.
flutter build appbundle --obfuscate --split-debug-info=build/symbolsЧитайте отчёты о сбоях от тестировщиков
Тестировщики вставляют трассировки стека в отчёты об ошибках, а Play Console собирает сбои в разделе Мониторинг и улучшение → Android Vitals → Сбои и ANR. У трассировки Flutter две половины: часть Dart, которую --obfuscate превращает в короткие бессмысленные имена, и часть Android, которую сжимает R8. Flutter всегда включает сжатие кода в релизных сборках, поэтому готовьтесь к обеим.
Сохраняйте символы Dart
Сборка с
--split-debug-infoзаписывает по файлу символов на архитектуру, напримерapp.android-arm64.symbols. Храните папку вместе с кодом версии, к которому она относится: файл от другой сборки не расшифрует трассировку.Расшифровывайте трассировки Dart
Сохраните трассировку в файл и передайте её
flutter symbolizeвместе с файлом символов для архитектуры устройства. Большинство современных телефонов работают на arm64.Java-часть оставьте Play Console
Начиная с Android Gradle plugin 4.1, файл сопоставления R8 входит в
.aab, поэтому Play Console деобфусцирует строки Java и Kotlin из плагинов без отдельной загрузки.
flutter symbolize -i crash.txt -d build/symbols/app.android-arm64.symbolsОбфускация только переименовывает символы: она ничего не шифрует и не подходит для хранения секретов. Код, который сравнивает runtimeType.toString() с именем класса, ломается после обфускации, а имена enum остаются читаемыми.
Коротко о Play Console: от закрытого теста до продакшена
Когда бандл Flutter собран и подписан, всё остальное происходит в Play Console и одинаково для любого фреймворка. Здесь краткий чек-лист; каждое меню и экран описаны в полном руководстве по Play Console.
- Завершите настройку приложения на панели управления: политика конфиденциальности, доступ к приложению, реклама, возрастной рейтинг, целевая аудитория, безопасность данных и страница в Play Маркете.
- Создайте выпуск для закрытого теста в разделе Тестирование и выпуск → Тестирование → Закрытое тестирование: выберите страны тестировщиков, загрузите
.aabи отправьте на проверку. - Добавьте тестировщиков через Google Группу на вкладке Тестировщики и поделитесь ссылкой opt-in. Она заработает, когда у трека появится статус Опубликовано.
- Держите не менее 12 тестировщиков с opt-in 14 дней подряд. Внутреннее тестирование не засчитывается, а у того, кто вышел и вернулся, отсчёт 14 дней начинается заново.
- Выпустите хотя бы одно обновление во время теста и запишите, что нашли тестировщики и что вы изменили.
- Подайте заявку на доступ к продакшену с панели управления и ответьте на три раздела о тесте, приложении и готовности. Решение принимает Google; никто не может гарантировать одобрение.
Учитывайте выбывших: наберите больше 12 тестировщиков, чтобы уход одного человека не сдвинул ваши сроки. Все шаги в Play Console.
Полное руководство по Play Console
Закрытый трек, Google Группа и ссылка opt-in, правило 12 тестировщиков / 14 дней и заявка на продакшен, шаг за шагом на одной странице.
Выпускайте исправления во время теста
Во Flutter: увеличьте номер сборки, пересоберите и загрузите новый бандл в тот же закрытый трек.
flutter build appbundle --build-name=1.0.1 --build-number=2Перед каждой загрузкой запускайте релизную сборку на реальном телефоне. flutter install устанавливает релизный APK на подключённое устройство и за минуты выявляет недостающие разрешения, не тот flavor и сбои, которые бывают только в релизе:
flutter build apk
flutter installЧтобы проверить именно тот бандл, который вы собираетесь загрузить, превратите его в APK для устройства с помощью bundletool от Google или загрузите в закрытый трек и установите из Google Play с аккаунта тестировщика. Так вы тестируете то, что получают тестировщики, включая разделённые по устройствам APK от Play.
- Одно конкретное изменение на обновление. По небольшим выпускам видно, какое исправление ответило на какой отчёт, а в форме доступа к продакшену у вас будут конкретные примеры.
- Не меняйте версию Flutter посреди теста, если этого не требует ошибка от тестировщиков. Обновление SDK одновременно меняет движок, нативный код каждого плагина и, возможно,
targetSdk. - Оставьте время на проверку. Каждый новый выпуск в закрытом треке уходит в Google, прежде чем попасть к тестировщикам, поэтому не откладывайте последнее исправление на последний день.
Частые вопросы
Нужен ли --obfuscate для закрытого теста?
Нет. Обфускация необязательна и лишь затрудняет чтение символов Dart. Если вы её используете, всегда добавляйте --split-debug-info и архивируйте символы, иначе сбои от тестировщиков будет невозможно прочитать.
Какой код версии видит Google Play?
Число после + в pubspec.yaml или значение --build-number, если вы его передаёте. Flutter использует имя сборки как versionName, а номер сборки как versionCode.
Можно ли дать тестировщикам APK?
Не через Google Play: новые приложения загружаются только в формате .aab, и для 12 тестировщиков засчитываются только установки из закрытого трека. Но flutter build apk --split-per-abi удобен для быстрых проверок на ваших собственных телефонах.
Почему Play Console предупреждает о страницах памяти 16 КБ?
Обычно плагин поставляет нативные библиотеки .so, не выровненные под страницы 16 КБ. Обновите плагин и Flutter, пересоберите и проверьте результат на эмуляторе Android с системным образом со страницами 16 КБ до следующей загрузки.
Источники
Технические детали сверены с официальной документацией ниже. Меню и значения по умолчанию меняются от версии к версии; если сомневаетесь, ориентируйтесь на официальную страницу.
- Flutter: Build and release an Android app
- Flutter: Obfuscate Dart code
- Android Developers: Sign your app
- Android Developers: Target API level requirements
- Android Developers: Support 16 KB page sizes
- Android Developers: bundletool
- Play Console Help: App testing requirements for new personal developer accounts
- Play Console Help: Set up an open, closed, or internal test
- Play Console Help: Deobfuscate or symbolicate crash stack traces
Впервые слышите о правиле 12 тестировщиков?
Общее руководство объясняет требование Google, как день за днём проходит кампания TestBunny и что мы можем и не можем гарантировать.
Читать общее руководство