Настройка Dynamic Feature Modules для Android-приложения
Приложение для доставки еды имеет встроенный модуль AR-просмотра блюд — весит 23 MB дополнительных ресурсов. Эту функцию открывают 8% пользователей. Остальные 92% скачивают 23 MB, которые им не нужны никогда. Dynamic Feature Modules решает именно это: AR-модуль скачивается только тем, кто нажал «посмотреть в AR».
Архитектура проекта с DFM
Проект перестраивается на multi-module структуру. app становится base модулем — содержит только критичный для старта функционал. Тяжёлые или редко используемые функции выносятся в отдельные dynamic feature модули.
Структура build.gradle для DFM:
// 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) — модуль устанавливается вместе с приложением. Разница с обычным модулем в том, что он может поставляться как separate APK и участвует в слайсинге.
On-demand (dist:on-demand) — загружается по запросу через Play Feature Delivery API:
val manager = SplitInstallManagerFactory.create(context)
val request = SplitInstallRequest.newBuilder()
.addModule("feature_ar")
.build()
manager.startInstall(request)
.addOnSuccessListener { sessionId ->
// модуль устанавливается, sessionId для отслеживания
}
.addOnFailureListener { exception ->
// SplitInstallException — обрабатываем коды ошибок
}
Conditional (dist:conditions) — автоматическая установка при соответствии условиям: версия Android, наличие OpenGL ES, страна пользователя.
Проблемы при внедрении
Навигация. Из базового модуля нельзя импортировать классы из 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 зависит от base, никогда наоборот
- SplitCompat инициализирован во всех Application/Activity
- Все DFM-сценарии покрыты обработкой
REQUIRES_USER_CONFIRMATION - Bundletool-тест на реальных устройствах перед публикацией
- Deferred install для некритичных модулей — не блокируем пользователя
Сроки
Настройка одного DFM-модуля с навигацией — 3–5 дней. Перевод существующего монолитного приложения на multi-module с несколькими DFM — 2–4 недели в зависимости от сцепленности кода.







