Сборка, которая попадает в Google Play с весом 70–80 МБ, — это не редкая ошибка, а стандартный результат проекта на Flutter, который никто не измерял. Загрузка кажется нормальной на вашем офисном Wi-Fi и вашем Pixel, а потом пользователь на трёхлетнем Android-устройстве с 2 ГБ оперативной памяти ждёт, устанавливает и оставляет отзыв на одну звезду о том, что «медленно и тяжело». Исправление начинается не со списка советов. Оно начинается с цифры.
Миф первый: «Приложения на Flutter просто большие, это особенность фреймворка»
Flutter действительно поставляет среду выполнения, но фреймворк — это фиксированная стоимость, которую вы не можете убрать. Что вы можете убрать — это всё, что вы добавили сверху и никогда не проверяли. Честный первый шаг — flutter build appbundle --analyze-size (или --analyze-size для APK). Он создаёт профиль размера, который вы открываете в DevTools, и дерево разбивается по пакетам: ваш код на Dart, движок Flutter, ресурсы, нативные библиотеки, шрифты.
Большинство команд, которые запускают это впервые, находят одних и тех же трёх виновников: несжатые PNG и JPEG в папке assets/, полный шрифт иконок, подключённый ради восьми глифов, и два-три Firebase SDK, добавленных «на всякий случай» во время спринта и никогда не удалённых.
Миф второй: «split-per-abi — это устаревший трюк для Android»
Это не устаревший приём, это всё ещё один из самых дешёвых способов выиграть. Универсальный APK содержит нативные библиотеки для arm64-v8a, armeabi-v7a и x86_64 в одном файле. flutter build apk --split-per-abi создаёт три APK, каждый несёт только свою архитектуру — arm64 обычно на треть меньше универсальной сборки.
Но лучший вариант по умолчанию в 2026 году — Android App Bundle. flutter build appbundle позволяет Play автоматически доставлять раздельные сборки для каждого устройства, и вы сохраняете один артефакт. Подвох, который часто упускают: если вы тестируете локально только универсальный APK, вы никогда не видите, что скачивает реальное устройство, и не замечаете, что нативная зависимость поставляет библиотеки для архитектур, которые вы не поддерживаете. Загрузите в трек внутреннего тестирования и проверьте размер загрузки, который показывает Play — именно эта цифра, а не APK на вашем диске, определяет, что платит пользователь.
Миф третий: «Ресурсы маленькие, они не важны»
Одно главное изображение, экспортированное из инструмента дизайна с весом 3 МБ, стоит больше, чем все правки кода вместе взятые. Конвертируйте в WebP, ограничьте разрешение тем, что действительно нужно макету, и проверьте с помощью анализатора размера, какие ресурсы доминируют. То же касается шрифтов: файл вариативного шрифта с полным покрытием кириллицы и латиницы может весить больше, чем ваш код на Dart. Сделайте подмножество или оставьте только используемые начертания.
Иконки — это тихий виновник. flutter build по умолчанию удаляет неиспользуемые иконки из Material и выводит, сколько глифов было удалено — прочитайте эту строку в логе сборки. Если вы подключили собственный шрифт иконок или пакет, который поставляет свой, tree-shaking его не затрагивает. Вот где шрифт на 400 КБ отправляется ради шести иконок.
Миф четвёртый: «Прореживание для iOS — проблема Apple»
App thinning на iOS реально существует, но помогает только если ваша сборка настроена для него. Опция Enable bitcode исчезла в последних версиях Xcode, и оставшийся практический рычаг — каталоги ресурсов: изображения, помещённые в Assets.xcassets с правильными вариантами масштаба, прореживаются для каждого устройства, а изображения, сброшенные в папку Flutter assets/, — нет. Если размер загрузки для iOS выглядит раздутым по сравнению с Android, сначала посмотрите туда, прежде чем винить движок.
Ещё два рычага, о которых стоит знать:
--split-debug-infoи--obfuscateудаляют таблицы символов из релизного бинарника. Вы сохраняете файл символов для деобфускации стектрейсов при сбоях — потеряйте его, и ваши стектрейсы станут шумом, поэтому архивируйте его в CI.- Отложенные компоненты позволяют поставлять функцию как отдельную загрузку на Android. Мощно, но добавляет состояние загрузки и путь отказа, который теперь нужно тестировать. Не прибегайте к этому, пока не сделаны простые улучшения.
Как выглядит реалистичное «до/после»
Цифры ниже приведены для иллюстрации для приложения среднего размера с Firebase Auth, Firestore, экраном карт и лентой с обилием медиа — ваше дерево будет отличаться, но пропорции обычно сохраняются.
| Рычаг | До | После |
|---|---|---|
| Универсальный APK, устройство arm64 | ~78 МБ | — |
| App Bundle, загрузка на устройство | — | ~34 МБ |
| Ресурсы (PNG → WebP, изменённый размер) | ~14 МБ | ~4 МБ |
| Удалены неиспользуемые Firebase SDK | ~6 МБ | 0 |
| Шрифт иконок → tree-shaken Material | ~0,5 МБ | ~0,05 МБ |
Часть, которая действительно кусается: проверка, что ничего не сломалось
Каждый выигрыш в размере — это изменение поведения, пока не доказано обратное. То, что ловит команды, — офлайн: вы сжали ресурсы, сменили форматы, переместили шрифт — и приложение, которое работало онлайн, теперь показывает серые квадраты в режиме полёта. Тестируйте релизную сборку, а не отладочную, с отключённой сетью, на реальном маломощном устройстве. Проверьте, что изображения отображаются, иконки появляются, а экраны, работающие из кеша, по-прежнему заполняются.
Где структурированное обучение действительно помогает
Вы можете найти каждую команду выше в документации Flutter — барьер не в информации, а в знании, какой рычаг применим к вашему дереву и в каком порядке, и в том, чтобы кто-то проверил компромисс перед выпуском. Самостоятельное изучение даёт вам команды; оно редко даёт обратную связь по такому решению, как «стоят ли отложенные компоненты состояния загрузки здесь?»
Курс Flutter и Dart — кроссплатформенная разработка охватывает конвейер сборки, интеграцию с Firebase, работу с ресурсами и публикацию в магазинах в том же порядке, в котором вы с ними сталкиваетесь в реальном проекте, с уроками, адаптированными под ваш уровень — полезно, если вы хотите охватить сторону релиза, а не открывать её для себя в день запуска.
Размер — это бюджет, а не разовая уборка. Установите потолок, проверяйте цифру загрузки, которую сообщают Play и App Store Connect при каждом релизе, и воспринимайте скачок как сигнал снова открыть анализатор.