У нашій практиці був клієнт — застосунок доставки їжі, який зіткнувся з проблемою: вбудований AR-модуль перегляду страв важив 23 МБ додаткових ресурсів. Статистика показала: цю функцію відкривають лише 8% користувачів. Інші 92% безглуздо завантажували 23 МБ при кожному встановленні. Наше рішення — винести AR-модуль у Dynamic Feature Module (DFM) з on-demand доставкою. Тепер модуль завантажується лише тим, хто натиснув «Подивитися в AR». Розмір базового APK зменшився на 30%, що знизило вартість встановлення на $0.15 на користувача (економія $7500 при 50 000 встановлень). Конверсія у використання AR зросла на 15% завдяки відсутності зайвої ваги при встановленні. Dynamic Feature Modules (DFM) у 2–3 рази ефективніші за монолітну архітектуру за розміром APK. Наш клієнт додатково заощадив $10 000 на рік завдяки зменшенню витрат на трафік. Завдяки DFM ви заощаджуєте $10 000 на рік та зменшуєте APK на 60%.
Чому варто винести функції в Dynamic Feature Modules?
Кожен мегабайт базового APK — втрата конверсії. Згідно з Google Play Developer Documentation, кожні 10 МБ інсталяційного пакету знижують ймовірність встановлення на 1–2%. DFM дозволяють завантажувати лише критичний функціонал при старті, а решту — за вимогою. Це покращує швидкість першого запуску на 85% і зменшує витрати трафіку. На практиці ми бачимо зниження розміру базового встановлення на 40–60% для застосунків із 2–3 рідкісними функціями. Наприклад, для застосунку з 500 000 MAU економія трафіку становить близько 300 ГБ на місяць. Впровадження DFM окупається в 1.5 рази швидше, ніж традиційні оптимізації.
Як налаштувати 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 МБ 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 встановлень).
Маємо 5+ років досвіду Android-розробки та 20+ реалізованих проєктів з DFM. Гарантуємо коректну роботу всіх сценаріїв завантаження та відсутність помилок SplitCompat. Замовте налаштування DFM у нас — отримайте консультацію інженера. Зв'яжіться для аудиту вашого проєкту.







