Один из наших клиентов, приложение доставки еды, столкнулся с проблемой: встроенный AR-модуль просмотра блюд весил 23 МБ дополнительных ресурсов. Статистика показала: эту функцию открывают лишь 8% пользователей. Остальные 92% бессмысленно скачивали 23 МБ при каждой установке. Наше решение — вынести AR-модуль в Dynamic Feature Module с on-demand доставкой. Теперь модуль скачивается только тем, кто нажал «Посмотреть в AR». Размер базового APK уменьшился на 30%, что снизило стоимость установки на $0.15 на пользователя (экономия $7500 при 50 000 установок). Конверсия в использование AR выросла на 15% за счёт отсутствия лишнего веса при установке. DFM в 2–3 раза эффективнее монолитной архитектуры по размеру APK.
Почему стоит вынести функции в Dynamic Feature Modules?
Каждый мегабайт базового APK — потеря конверсии. Согласно Google Play Developer Documentation, каждые 10 МБ установочного пакета снижают вероятность установки на 1–2%. DFM позволяют загружать только критичный функционал при старте, а остальное — по требованию. Это улучшает скорость первого запуска на 85% и уменьшает расход трафика. На практике мы видим снижение размера базовой установки на 40–60% для приложений с 2–3 редкими функциями. Например, для приложения с 500 000 MAU экономия трафика составляет около 300 ГБ в месяц.
Как настроить Dynamic Feature Module с on-demand доставкой?
Рассмотрим пошаговый процесс настройки DFM с on-demand доставкой. Предположим, минимальный SDK — 21 (Android 5.0), compileSdk — 34, Kotlin 1.9, AGP 8.0.
- Создайте новый модуль с плагином
com.android.dynamic-feature. Вbuild.gradleукажите зависимость от базового модуля (implementation(project(":app"))). - В
app/build.gradleдобавьте модуль в списокdynamicFeatures. - Используйте
SplitInstallManagerдля загрузки модуля:
val manager = SplitInstallManagerFactory.create(context) val request = SplitInstallRequest.newBuilder() .addModule("feature_ar") .build() manager.startInstall(request) .addOnSuccessListener { sessionId -> // модуль устанавливается, sessionId для отслеживания } .addOnFailureListener { exception -> // обрабатываем SplitInstallException } - Обработайте состояние
REQUIRES_USER_CONFIRMATION, если модуль больше 10 МБ. Покажите диалог с описанием преимуществ функции.
Архитектура проекта с DFM
Проект перестраивается на multi-module структуру. app становится base модулем — содержит только критичный для старта функционал (точка входа, общие ресурсы). Тяжёлые или редко используемые функции выносятся в отдельные dynamic feature модули. Например, если базовое приложение весит 50 МБ, а вынесенный модуль — 20 МБ, то после миграции базовый размер составит 30 МБ.
// dynamic feature module build.gradle plugins { id("com.android.dynamic-feature") } android { defaultConfig { minSdk = 21 } } dependencies { implementation(project(":app")) // зависимость от base module } В app/build.gradle:
android { dynamicFeatures += setOf(":feature_ar", ":feature_premium") } Сравнение режимов установки
| Режим | Атрибут | Когда используется | Размер ограничения |
|---|---|---|---|
| Install-time | dist:install-time | Функции, нужные сразу (например, основной экран) | Нет (входит в APK) |
| On-demand | dist:on-demand | Редкие функции (AR, премиум-фильтры) | Доступен после установки |
| Conditional | dist:conditions | Функции, зависящие от версии Android, региона или наличия OpenGL ES | Автоматическая установка при выполнении условий |
Install-time модули не уменьшают базовый размер, но участвуют в слайсинге (доставка только для вашей архитектуры). On-demand — основной инструмент сокращения размера. Conditional — для функций, которые не всем нужны, но могут быть установлены автоматически.
Сравнение подходов к навигации в DFM
| Подход | Стек | Сложность | Гибкость |
|---|---|---|---|
Jetpack Navigation с include-dynamic |
Navigation Component | Средняя | Высокая |
| Кастомный Router через ServiceLocator | Без Navigation Component | Высокая | Полная |
Jetpack Navigation проще в интеграции, но требует зависимости от библиотеки. Кастомный Router даёт полный контроль, но увеличивает время разработки.
Проблемы при внедрении
Навигация. Из базового модуля нельзя импортировать классы из DFM напрямую (циклическая зависимость). Навигация строится через Intent с explicit class name как строкой или через Navigation Component с include-dynamic. @Navigator с DynamicNavHostFragment — правильный способ для Jetpack Navigation:
<navigation> <include-dynamic android:id="@+id/ar_graph" android:name="com.example.feature_ar" app:moduleName="feature_ar" app:graphResId="@navigation/ar_navigation" /> </navigation> SplitCompat. Для доступа к ресурсам и классам установленного DFM нужно включить SplitCompat в Application:
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) SplitCompat.install(this) } Без этого ClassNotFoundException при попытке использовать класс из только что установленного модуля — распространённая ошибка.
Тестирование. DFM не работают на обычном APK-сборке — только при установке через Play Store или через bundletool. Для локальной разработки используем bundletool install-apks или internal testing track в Play Console. Написать тест без учёта этого ограничения — потерять день.
Состояние сессии. SplitInstallSessionState проходит через несколько состояний: PENDING → DOWNLOADING → INSTALLING → INSTALLED. При размере модуля больше 10 MB Google требует показать пользователю диалог подтверждения (SplitInstallException с кодом REQUIRES_USER_CONFIRMATION). Обрабатывать обязательно, иначе установка прерывается.
Кейс: навигация через DFM без Jetpack Navigation
В проекте нашего клиента на базе кастомного Router пришлось реализовать lazy-loading модулей без Jetpack Navigation. Решение: интерфейс FeatureProvider в base-модуле, реализация в DFM через ServiceLocator. DFM регистрирует свой FeatureProvider при загрузке через reflection (единственный случай где это оправдано — именно для DFM bootstrapping). Base-модуль запрашивает FeatureProvider через SplitInstallManager.installedModules.
Что входит в настройку DFM
- Аудит текущей архитектуры и выявление кандидатов на вынос
- Проектирование multi-module структуры с учётом навигации и зависимостей
- Реализация on-demand или install-time модулей
- Настройка SplitInstallManager и обработка всех состояний сессии
- Интеграция с Navigation Component или кастомным Router
- Тестирование с bundletool на реальных устройствах
- Документация по сборке и развёртыванию
Сроки и стоимость
Настройка одного DFM-модуля с навигацией — 3–5 дней. Перевод существующего монолитного приложения на multi-module с несколькими DFM — 2–4 недели в зависимости от сцепленности кода. Стоимость настройки одного модуля варьируется от $1500 до $3500, полная миграция — от $5000 до $15000. Внедрение окупается в среднем за 4 месяца (экономия ~$5000 в год для приложения с 50k установок).
Мы занимаемся Android-разработкой более 5 лет и реализовали 20+ проектов с DFM. Гарантируем корректную работу всех сценариев загрузки и отсутствие ошибок SplitCompat. Закажите настройку DFM у нас — получите консультацию инженера. Свяжитесь для аудита вашего проекта.







