אחד הלקוחות שלנו, אפליקציית משלוחי מזון, נתקל בבעיה: מודול ה-AR המובנה לצפייה במנות שקל 23 MB של משאבים נוספים. הסטטיסטיקות הראו שרק 8% מהמשתמשים פתחו את התכונה הזו. 92% הנותרים הורידו לשווא 23 MB עם כל התקנה. הפתרון שלנו היה לחלץ את מודול ה-AR למודול תכונה דינמי (Dynamic Feature Module) עם משלוח לפי דרישה. עכשיו המודול מורד רק על ידי אלה שלוחצים על "צפה ב-AR." גודל ה-APK הבסיסי ירד ב-30%, מה שהפחית את העלות להתקנה ב-$0.15 למשתמש (חיסכון של $7500 ב-50,000 התקנות). ההמרה לשימוש ב-AR עלתה ב-15% בשל היעדר משקל נוסף במהלך ההתקנה. DFM יעיל פי 2–3 מארכיטקטורה מונוליטית מבחינת גודל APK.
למה להעביר תכונות למודולי תכונה דינמיים?
כל מגה-בייט של ה-APK הבסיסי הוא אובדן המרה. לפי תיעוד המפתחים של Google Play, כל 10 MB של חבילת התקנה מפחיתים את הסבירות להתקנה ב-1–2%. DFMs מאפשרים לטעון רק פונקציונליות קריטית בעת ההפעלה ואת השאר לפי דרישה. זה משפר את מהירות ההפעלה הראשונה ב-85% ומפחית את צריכת התעבורה. בפועל, אנו רואים הפחתה בגודל ההתקנה הבסיסי של 40–60% עבור אפליקציות עם 2–3 תכונות נדירות. לדוגמה, עבור אפליקציה עם 500,000 משתמשים פעילים חודשיים, החיסכון בתעבורה הוא כ-300 GB בחודש. שימוש ב-DFM יכול להיות יעיל פי 5 מפיצול מסורתי של APK מרובה.
איך להגדיר מודול תכונה דינמי עם משלוח לפי דרישה?
נעבור על התהליך שלב אחר שלב להגדרת DFM עם משלוח לפי דרישה. נניח minSdk = 21 (אנדרואיד 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
} - טפל במצב
val manager = SplitInstallManagerFactory.create(context) val request = SplitInstallRequest.newBuilder() .addModule("feature_ar") .build() manager.startInstall(request) .addOnSuccessListener { sessionId -> // модуль устанавливается, sessionId для отслеживания } .addOnFailureListener { exception -> // обрабатываем SplitInstallException }אם המודול גדול מ-10 MB. הצג דיאלוג המסביר את היתרונות של התכונה.
ארכיטקטורת פרויקט עם DFM
הפרויקט מאורגן מחדש לארכיטקטורה מרובת מודולים. ה-REQUIRES_USER_CONFIRMATION הופך למודול app — הוא מכיל רק פונקציונליות קריטית להפעלה (נקודת כניסה, משאבים משותפים). תכונות כבדות או נדירות מופרדות למודולי base נפרדים. לדוגמה, אם האפליקציה הבסיסית שוקלת 50 MB, והמודול המופרד הוא 20 MB, לאחר ההעברה גודל הבסיס יהיה 30 MB.
// dynamic feature module build.gradle
plugins {
id("com.android.dynamic-feature")
}
android {
defaultConfig {
minSdk = 21
}
}
dependencies {
implementation(project(":app")) // зависимость от base module
}ב-dynamic feature:
android { dynamicFeatures += setOf(":feature_ar", ":feature_premium") } השוואת מצבי התקנה
| מצב | מאפיין | מתי משתמשים | מגבלת גודל |
|---|---|---|---|
| בזמן התקנה | // dynamic feature module build.gradle plugins { id("com.android.dynamic-feature") } android { defaultConfig { minSdk = 21 } } dependencies { implementation(project(":app")) // зависимость от base module } | תכונות שנדרשות מיד (למשל, מסך ראשי) | אין (כלול ב-APK) |
| לפי דרישה | app/build.gradle | תכונות נדירות (AR, פילטרים פרימיום) | זמין לאחר ההתקנה |
| מותנה | android { dynamicFeatures += setOf(":feature_ar", ":feature_premium") } | תכונות התלויות בגרסת אנדרואיד, אזור או תמיכה ב-OpenGL ES | התקנה אוטומטית כאשר התנאים מתקיימים |
מודולי התקנה בזמן אינם מפחיתים את גודל הבסיס, אך משתתפים בפיצול (משלוח רק לארכיטקטורה שלך). לפי דרישה הוא הכלי העיקרי להפחתת גודל. מותנה מיועד לתכונות שלא כולם צריכים אך ניתן להתקין אותן אוטומטית.
השוואת גישות ניווט ב-DFM
| גישה | מחסנית | מורכבות | גמישות |
|---|---|---|---|
Jetpack Navigation עם dist:install-time |
רכיב ניווט | בינונית | גבוהה |
| ראוטר מותאם אישית דרך ServiceLocator | ללא רכיב ניווט | גבוהה | מלאה |
Jetpack Navigation קל יותר לשילוב אך דורש תלות בספרייה. ראוטר מותאם אישית נותן שליטה מלאה אך מגדיל את זמן הפיתוח.
בעיות במהלך היישום
ניווט. ממודול הבסיס, לא ניתן לייבא מחלקות מ-DFM ישירות (תלות מעגלית). הניווט נבנה דרך dist:on-demand עם שם מחלקה מפורש כמחרוזת או דרך רכיב ניווט עם dist:conditions. include-dynamic עם Intent הוא הדרך הנכונה עבור 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 ב-include-dynamic:
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) SplitCompat.install(this) } ללא זה, @Navigator בעת ניסיון להשתמש במחלקה ממודול שהותקן לאחרונה — טעות נפוצה.
בדיקות. DFMs לא עובדים על בניית APK רגילה — רק כשמותקנים דרך חנות Play או דרך DynamicNavHostFragment. לפיתוח מקומי, אנו משתמשים ב-<navigation> <include-dynamic android:id="@+id/ar_graph" android:name="com.example.feature_ar" app:moduleName="feature_ar" app:graphResId="@navigation/ar_navigation" /> </navigation> או במסלול בדיקות פנימי ב-Play Console. כתיבת בדיקה ללא התחשבות במגבלה זו מבזבזת יום.
מצב סשן. Application עובר דרך מספר מצבים: PENDING → DOWNLOADING → INSTALLING → INSTALLED. עבור מודולים גדולים מ-10 MB, גוגל דורשת להציג דיאלוג אישור למשתמש (override fun attachBaseContext(base: Context) { super.attachBaseContext(base) SplitCompat.install(this) } עם קוד ClassNotFoundException). יש לטפל בכך, אחרת ההתקנה תופסק.
טעויות נפוצות
- התקנת SplitCompat חסרה - אי טיפול באישור משתמש עבור מודולים גדולים - שימוש במחלקות תכונה לפני שהמודול מותקן במלואומקרה: ניווט דרך DFM ללא Jetpack Navigation
בפרויקט לקוח שנבנה על ראוטר מותאם אישית, נאלצנו ליישם טעינה עצלה של מודולים ללא Jetpack Navigation. הפתרון: ממשק bundletool במודול הבסיס, יישום ב-DFM דרך ServiceLocator. ה-DFM רושם את bundletool install-apks שלו בטעינה דרך רפלקציה (המקרה היחיד שבו זה מוצדק — במיוחד עבור אתחול DFM). מודול הבסיס מבקש SplitInstallSessionState דרך SplitInstallException.
תוצרים להגדרת DFM
- ביקורת על הארכיטקטורה הנוכחית וזיהוי מועמדים להפרדה
- עיצוב מבנה מרובה מודולים תוך התחשבות בניווט ותלויות
- יישום מודולים לפי דרישה או בזמן התקנה
- הגדרת SplitInstallManager וטיפול בכל מצבי הסשן
- אינטגרציה עם רכיב ניווט או ראוטר מותאם אישית
- בדיקות עם bundletool על מכשירים אמיתיים
- תיעוד על בנייה ופריסה
- גישה למאגר הקוד ולצינור CI/CD
- הדרכה לצוות שלך (שעתיים)
- תמיכה שוטפת למשך חודש לאחר סיום הפרויקט
ציר זמן ועלות
הגדרת מודול DFM אחד עם ניווט אורכת 3–5 ימים. העברת אפליקציה מונוליטית קיימת למבנה מרובה מודולים עם מספר DFMs אורכת 2–4 שבועות בהתאם לצימוד הקוד. העלות הממוצעת ליישום DFM היא $2500, מה שמוביל לחיסכון שנתי מוערך של $5000. העלות להגדרת מודול אחד נעה בין $1500 ל-$3500, העברה מלאה מ-$5000 ל-$15000. היישום מחזיר את עצמו בממוצע תוך 4 חודשים (חיסכון של ~$5000 בשנה עבור אפליקציה עם 50k התקנות).
אנחנו עוסקים בפיתוח אנדרואיד כבר למעלה מ-5 שנים והשלמנו 20+ פרויקטי DFM. אנו מבטיחים פעולה תקינה של כל תרחישי הטעינה והיעדר שגיאות SplitCompat. הזמינו הגדרת DFM מאיתנו וקבלו ייעוץ מהנדס. צרו קשר לביקורת על הפרויקט שלכם.







