אם לאפליקציה יש minSdk של API 20 או גרסה ישנה יותר, והאפליקציה והספריות שהיא מפנה אליהן חורגות מ-65,536 שיטות, תופיע שגיאת ה-build הבאה שמציינת שהאפליקציה הגיעה למגבלה של ארכיטקטורת ה-build של Android:
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
בגרסאות ישנות יותר של מערכת ה-build מדווחת שגיאה אחרת, שמעידה על אותה בעיה:
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
במצבי השגיאה האלה מוצג מספר נפוץ: 65536. המספר הזה מייצג את המספר הכולל של קובצי העזר שאפשר להפעיל באמצעות הקוד בתוך קובץ בייטקוד אחד של Dalvik Executable (DEX). בדף הזה מוסבר איך לעקוף את המגבלה הזו על ידי הפעלת הגדרת אפליקציה שנקראת multidex, שמאפשרת לאפליקציה ליצור ולקרוא כמה קובצי DEX.
מידע על מגבלת 64K קובצי העזר
קובצי אפליקציות ל-Android (APK) מכילים קובצי בייטקוד להפעלה בפורמט Dalvik Executable (DEX), שמכילים את הקוד המקומפל שמשמש להפעלת האפליקציה. המפרט של Dalvik Executable מגביל את המספר הכולל של השיטות שאפשר להפנות אליהן בקובץ DEX יחיד ל-65,536 – כולל שיטות של Android framework, שיטות של ספריות ושיטות בקוד שלכם.
בהקשר של מדעי המחשב, המונח קילו, או K, מציין 1,024 (או 2 בחזקת 10). 65,536 שווה ל-64x1024, ולכן המגבלה הזו נקראת <em>מגבלת ההפניות של 64K</em>.תמיכה ב-multidex בגרסאות שקודמות ל-Android 5.0
בגרסאות של הפלטפורמה שקדמו ל-Android 5.0 (רמת API 21), נעשה שימוש בסביבת זמן הריצה של Dalvik כדי להריץ את קוד האפליקציה. כברירת מחדל, Dalvik מגביל את האפליקציות לקובץ בייטקוד classes.dex אחד לכל APK. כדי לעקוף את המגבלה הזו, מוסיפים את ספריית ה-multidex לקובץ build.gradle או build.gradle.kts ברמת המודול:
מגניב
dependencies { def multidex_version = "2.0.1" implementation "androidx.multidex:multidex:$multidex_version" }
Kotlin
dependencies { val multidex_version = "2.0.1" implementation("androidx.multidex:multidex:$multidex_version") }
הספרייה הזו הופכת לחלק מקובץ ה-DEX הראשי של האפליקציה, ואז מנהלת את הגישה לקובצי ה-DEX הנוספים ולקוד שהם מכילים. כדי לראות את הגרסאות הנוכחיות של הספרייה הזו, עיינו ב גרסאות של multidex.
מידע נוסף זמין בקטע הגדרת האפליקציה ל-multidex.תמיכה ב-multidex ב-Android מגרסה 5.0 ואילך
ב-Android מגרסה 5.0 (רמת API 21) ומעלה נעשה שימוש בסביבת זמן ריצה שנקראת ART, שתומכת באופן מובנה בטעינה של כמה קובצי DEX מקובצי APK. ART מבצע קימפול מראש בזמן התקנת האפליקציה, סורק קובצי classesN.dex ומקמפל אותם לקובץ OAT אחד להפעלה במכשיר Android. לכן, אם הערך של minSdkVersion הוא 21 ומעלה, multidex מופעל כברירת מחדל ואין צורך בספריית multidex.
מידע נוסף על סביבת זמן הריצה של Android 5.0 זמין במאמר סביבת זמן ריצה ל-Android (ART) ו-Dalvik.
הערה: כשמפעילים את האפליקציה באמצעות Android Studio, ה-build עובר אופטימיזציה למכשירי היעד שבהם פורסים אותו. זה כולל הפעלה של multidex כשמכשירי היעד פועלים עם Android מגרסה 5.0 ואילך. מכיוון שהאופטימיזציה הזו חלה רק כשפורסים את האפליקציה באמצעות Android Studio, יכול להיות שעדיין תצטרכו להגדיר את גרסת ה-build להפצה ל-multidex כדי להימנע ממגבלת ה-64K.
איך להימנע מהמגבלה של 64K
לפני שמגדירים את האפליקציה כך שתאפשר שימוש ב-64,000 הפניות לשיטות או יותר, צריך לבצע פעולות כדי לצמצם את המספר הכולל של ההפניות שקוד האפליקציה קורא להן, כולל שיטות שמוגדרות על ידי קוד האפליקציה או ספריות כלולות.
האסטרטגיות הבאות יכולות לעזור לכם להימנע מהגעה למגבלת ההפניות של DEX:
- בדיקת יחסי התלות הישירים והטרנזיטיביים של האפליקציה
- כדאי לשקול אם הערך של כל ספרייה גדולה שאתם כוללים באפליקציה עולה על כמות הקוד שנוספת לאפליקציה. דפוס נפוץ אך בעייתי הוא לכלול ספרייה גדולה מאוד כי כמה שיטות שירות היו שימושיות. צמצום יחסי התלות של קוד האפליקציה יכול לעזור לכם להימנע ממגבלת ההפניות של DEX.
- הסרת קוד שלא בשימוש באמצעות R8
- מפעילים כיווץ קוד כדי להריץ את R8 בגרסאות ה-build להפצה. כדאי להפעיל כיווץ כדי לוודא שאתם לא שולחים קוד שלא בשימוש עם קובצי ה-APK. אם כיווץ הקוד מוגדר בצורה נכונה, הוא יכול גם להסיר קוד ומשאבים שלא בשימוש מהתלויות שלכם.
השיטות האלה יכולות לעזור לכם להקטין את הגודל הכולל של ה-APK, וכך לא תצטרכו להשתמש ב-multidex באפליקציה.
הגדרת האפליקציה ל-multidex
הערה: אם הערך שלminSdkVersion מוגדר ל-21 ומעלה, multidex מופעל כברירת מחדל ואין צורך בספריית multidex.
אם הערך של minSdkVersion הוא 20 או פחות, צריך להשתמש בספריית multidex ולבצע את השינויים הבאים בפרויקט של האפליקציה:
-
משנים את הקובץ
build.gradleברמת המודול כדי להפעיל את multidex ולהוסיף את ספריית multidex כתלות, כמו שמוצג כאן:מגניב
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 36 multiDexEnabled true } ... } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... minSdk = 15 targetSdk = 36 multiDexEnabled = true } ... } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
- בהתאם למצב שבו אתם מבטלים את המחלקה
Application, מבצעים אחת מהפעולות הבאות:אם לא משנים את המחלקה
Application, עורכים את קובץ המניפסט כדי להגדיר אתandroid:nameבתג<application>באופן הבא:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application android:name="androidx.multidex.MultiDexApplication" > ... </application> </manifest>
אם אתם משנים את המחלקה
Application, צריך לשנות אותה כך שתרחיב אתMultiDexApplication, באופן הבא:Kotlin
class MyApplication : MultiDexApplication() {...}
Java
public class MyApplication extends MultiDexApplication { ... }
אם אתם משנים את המחלקה
Applicationאבל אי אפשר לשנות את המחלקה הבסיסית, אתם יכולים לשנות את השיטהattachBaseContext()ולקרוא ל-MultiDex.install(this)כדי להפעיל multidex:Kotlin
class MyApplication : SomeOtherApplication() { override fun attachBaseContext(base: Context) { super.attachBaseContext(base) MultiDex.install(this) } }
Java
public class MyApplication extends SomeOtherApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
זהירות: אל תפעילו את
MultiDex.install()או כל קוד אחר באמצעות השתקפות או JNI לפני ש-MultiDex.install()מסתיים. מעקב multidex לא יפעל לפי הקריאות האלה, ויגרום לשגיאותClassNotFoundExceptionאו לשגיאות אימות בגלל חלוקה שגויה של מחלקות בין קובצי DEX.
עכשיו, כשאתם יוצרים את האפליקציה, כלי ה-build של Android יוצרים קובץ DEX ראשי (classes.dex) וקובצי DEX תומכים (classes2.dex, classes3.dex וכו') לפי הצורך.
לאחר מכן, מערכת ה-build אורזת את כל קובצי ה-DEX ב-APK.
בזמן הריצה, במקום לחפש רק בקובץ classes.dex הראשי, ממשקי ה-API של multidex משתמשים ב-class loader מיוחד כדי לחפש את השיטות בכל קובצי ה-DEX הזמינים.
המגבלות של ספריית multidex
לספריית multidex יש כמה מגבלות ידועות. כשמשלבים את הספרייה בתצורת ה-build של האפליקציה, חשוב להביא בחשבון את הנקודות הבאות:
- ההתקנה של קובצי DEX במהלך ההפעלה במחיצת הנתונים של המכשיר היא מורכבת, ויכולה לגרום לשגיאות מסוג "האפליקציה לא מגיבה" (ANR) אם קובצי ה-DEX המשניים גדולים. כדי להימנע מהבעיה הזו, הפעילו כיווץ קוד כדי להקטין את הגודל של קובצי DEX ולהסיר חלקים בקוד שלא בשימוש.
- כשמפעילים גרסאות שקודמות ל-Android 5.0 (רמת API 21), השימוש ב-multidex לא מספיק כדי לעקוף את מגבלת linearalloc (בעיה 37008143). המגבלה הזו הוגדלה ב-Android 4.0 (רמת API 14), אבל זה לא פתר את הבעיה לגמרי.
בגרסאות ישנות יותר מ-Android 4.0, יכול להיות שתגיעו למגבלת ה-linearalloc לפני שתגיעו למגבלת האינדקס של DEX. לכן, אם אתם מטרגטים רמות API נמוכות מ-14, כדאי לבדוק ביסודיות את הגרסאות האלה של הפלטפורמה, כי יכול להיות שיהיו בעיות באפליקציה בזמן ההפעלה או כשקבוצות מסוימות של מחלקות נטענות.
כיווץ הקוד יכול לצמצם או אולי אפילו למנוע את הבעיות האלה.
הצהרה על המחלקות הנדרשות בקובץ ה-DEX הראשי
כשיוצרים כל קובץ DEX לאפליקציית multidex, כלי ה-build מבצעים תהליך מורכב של קבלת החלטות כדי לקבוע אילו מחלקות נדרשות בקובץ ה-DEX הראשי, כדי שהאפליקציה תוכל לפעול בהצלחה. אם מחלקה שנדרשת במהלך ההפעלה לא מסופקת בקובץ ה-DEX הראשי, האפליקציה קורסת עם השגיאה java.lang.NoClassDefFoundError.
כלי ה-build מזהים את נתיבי הקוד של קוד שניגשים אליו ישירות מקוד האפליקציה. עם זאת, הבעיה הזו יכולה להתרחש כשנתיבי הקוד פחות גלויים, למשל כשבספרייה שבה אתם משתמשים יש יחסי תלות מורכבים. לדוגמה, אם הקוד משתמש באינטרוספקציה או בהפעלה של שיטות Java מקוד Native, יכול להיות שהמחלקות האלה לא יזוהו כנדרשות בקובץ ה-DEX הראשי.
אם מקבלים את השגיאה java.lang.NoClassDefFoundError, צריך לציין באופן ידני את המחלקות הנוספות שנדרשות בקובץ ה-DEX הראשי, על ידי הצהרה עליהן באמצעות המאפיין multiDexKeepProguard בסוג ה-build. אם יש התאמה למחלקה בקובץ multiDexKeepProguard, המחלקה הזו מתווספת לקובץ ה-DEX הראשי.
המאפיין multiDexKeepProguard
הקובץ multiDexKeepProguard משתמש באותו פורמט כמו ProGuard ותומך בכל הדקדוק של ProGuard. מידע נוסף על התאמה אישית של מה שנשמר באפליקציה זמין במאמר התאמה אישית של הקוד שרוצים לשמור.
הקובץ שציינת ב-multiDexKeepProguard צריך להכיל -keep
אפשרויות בכל תחביר ProGuard חוקי. לדוגמה,
-keep com.example.MyClass.class. אפשר ליצור קובץ בשם multidex-config.pro שנראה כך:
-keep class com.example.MyClass -keep class com.example.MyClassToo
אם רוצים לציין את כל המחלקות בחבילה, הקובץ נראה כך:
-keep class com.example.** { *; } // All classes in the com.example package
לאחר מכן, אפשר להצהיר על הקובץ הזה לסוג build, באופן הבא:
מגניב
android { buildTypes { release { multiDexKeepProguard file('multidex-config.pro') ... } } }
Kotlin
android { buildTypes { getByName("release") { multiDexKeepProguard = file("multidex-config.pro") ... } } }
אופטימיזציה של multidex בגרסאות build לפיתוח
הגדרת multidex דורשת זמן עיבוד ארוך משמעותית של ה-build, כי מערכת ה-build צריכה לקבל החלטות מורכבות לגבי המחלקות שצריך לכלול בקובץ ה-DEX הראשי והמחלקות שאפשר לכלול בקובצי ה-DEX המשניים. כלומר, בדרך כלל לוקח יותר זמן ליצור גרסאות build מצטברות באמצעות multidex, וזה עלול להאט את תהליך הפיתוח.
כדי לקצר את זמני ה-build המצטברים, אפשר להשתמש ב-pre-dexing כדי לעשות שימוש חוזר בפלט של multidex בין גרסאות build.
ה-Pre-dexing מסתמך על פורמט ART שזמין רק ב-Android מגרסה 5.0 (רמת API 21) ואילך. אם אתם משתמשים ב-Android Studio, סביבת הפיתוח המשולבת (IDE) משתמשת באופן אוטומטי ב-pre-dexing כשאתם פורסים את האפליקציה במכשיר עם Android בגרסה 5.0 (רמת API 21) ומעלה.
עם זאת, אם אתם מריצים גרסאות build של Gradle משורת הפקודה, אתם צריכים להגדיר את minSdkVersion ל-21 ומעלה כדי להפעיל את ה-pre-dexing.
minSdkVersion, כמו בדוגמה הבאה:
מגניב
android { defaultConfig { ... multiDexEnabled true // The default minimum API level you want to support. minSdkVersion 15 } productFlavors { // Includes settings you want to keep only while developing your app. dev { // Enables pre-dexing for command-line builds. When using // Android Studio 2.3 or higher, the IDE enables pre-dexing // when deploying your app to a device running Android 5.0 // (API level 21) or higher, regardless of minSdkVersion. minSdkVersion 21 } prod { // If you've configured the defaultConfig block for the production version of // your app, you can leave this block empty and Gradle uses configurations in // the defaultConfig block instead. You still need to include this flavor. // Otherwise, all variants use the "dev" flavor configurations. } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... multiDexEnabled = true // The default minimum API level you want to support. minSdk = 15 } productFlavors { // Includes settings you want to keep only while developing your app. create("dev") { // Enables pre-dexing for command-line builds. When using // Android Studio 2.3 or higher, the IDE enables pre-dexing // when deploying your app to a device running Android 5.0 // (API level 21) or higher, regardless of minSdkVersion. minSdk = 21 } create("prod") { // If you've configured the defaultConfig block for the production version of // your app, you can leave this block empty and Gradle uses configurations in // the defaultConfig block instead. You still need to include this flavor. // Otherwise, all variants use the "dev" flavor configurations. } } buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } } } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
במאמר אופטימיזציה של מהירות ה-build מפורטות אסטרטגיות נוספות שיעזרו לכם לשפר את מהירות ה-build מ-Android Studio או משורת הפקודה. מידע נוסף על השימוש בווריאנטים של build זמין במאמר הגדרת וריאנטים של build.
טיפ: אם יש לכם וריאנטים שונים של build לצרכים שונים של multidex, אתם יכולים לספק קובץ מניפסט שונה לכל וריאנט build, כך שרק הקובץ לרמת API 20 ומטה ישנה את שם התג <application>. אפשר גם ליצור מחלקת משנה שונה של Application לכל וריאנט, כך שרק מחלקת המשנה לרמת API 20 ומטה מרחיבה את המחלקה MultiDexApplication או קוראת ל-MultiDex.install(this).
בדיקת אפליקציות multidex
כשכותבים בדיקות אינסטרומנטציה לאפליקציות multidex, לא צריך הגדרות נוספות אם משתמשים באינסטרומנטציה של
MonitoringInstrumentation או של
AndroidJUnitRunner. אם אתם משתמשים ב-Instrumentation אחר, אתם צריכים לשנות את השיטה onCreate() שלו באמצעות הקוד הבא:
Kotlin
fun onCreate(arguments: Bundle) { MultiDex.install(targetContext) super.onCreate(arguments) ... }
Java
public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... }