אם באפליקציה שלכם minSdk של API 20 או גרסה מוקדמת יותר, והאפליקציה והספריות שהיא מפנה אליהן חורגות מ-65,536 שיטות, תופיע שגיאת הבנייה הבאה שמציינת שהאפליקציה הגיעה למגבלה של ארכיטקטורת הבנייה של 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. המספר הזה מייצג את המספר הכולל של ההפניות שאפשר להפעיל באמצעות הקוד בקובץ בייטקוד (bytecode) אחד של Dalvik Executable (DEX). בדף הזה מוסבר איך לעקוף את המגבלה הזו באמצעות הפעלת הגדרת אפליקציה שנקראת multidex, שמאפשרת לאפליקציה ליצור ולקרוא כמה קובצי DEX.
מידע על מגבלת ההפניה של 64K
קובצי Android app (APK) מכילים קובצי בייטקוד הפעלה בפורמט Dalvik Executable (DEX), שמכילים את הקוד שעבר קומפילציה ומשמש להפעלת האפליקציה. המפרט של Dalvik Executable מגביל את המספר הכולל של המתודות שאפשר להפנות אליהן בקובץ DEX יחיד ל-65,536 – כולל מתודות של מסגרת Android, מתודות של ספריות ומתודות בקוד שלכם.
בהקשר של מדעי המחשב, המונח קילו, או K, מציין 1,024 (או 2^10). מכיוון ש-65,536 שווה ל-64x1024, המגבלה הזו נקראת _מגבלת ההפניה של 64K_.תמיכה ב-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 Runtime (ART) and Dalvik.
הערה: כשמריצים את האפליקציה באמצעות Android Studio, ה-build עובר אופטימיזציה למכשירי היעד שבהם פורסתם. הפעולה הזו כוללת הפעלה של multidex כשהמכשירים שמוגדרים כיעד מריצים Android מגרסה 5.0 ואילך. האופטימיזציה הזו מתבצעת רק כשפורסים את האפליקציה באמצעות Android Studio, ולכן יכול להיות שעדיין תצטרכו להגדיר את גרסת ה-build להפצה שלכם ל-multidex כדי להימנע מהמגבלה של 64K.
איך להימנע מהמגבלה של 64K
לפני שמגדירים את האפליקציה כדי לאפשר שימוש ב-64K או יותר הפניות לשיטות, צריך לבצע פעולות כדי לצמצם את המספר הכולל של ההפניות שמופעלות על ידי קוד האפליקציה, כולל שיטות שמוגדרות על ידי קוד האפליקציה או ספריות שכלולות בה.
האסטרטגיות הבאות יכולות לעזור לכם להימנע מחריגה ממגבלת ההפניות ל-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()יסתיים. המעקב אחר קובצי DEX מרובים לא יכלול את הקריאות האלה, ולכן יופיעו שגיאותClassNotFoundExceptionאו שגיאות אימות בגלל חלוקת מחלקות שגויה בין קובצי DEX.
מעכשיו, כשמבצעים build של האפליקציה, כלי ה-build של Android יוצרים קובץ DEX ראשי (classes.dex) וקבצים תומכים של DEX (classes2.dex, classes3.dex וכן הלאה) לפי הצורך.
מערכת ה-build אורזת את כל קובצי ה-DEX לתוך ה-APK.
בזמן הריצה, במקום לחפש רק בקובץ classes.dex הראשי, ממשקי ה-API של multidex משתמשים בטוען מחלקות מיוחד כדי לחפש את השיטות שלכם בכל קובצי ה-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 לאפליקציה עם כמה קובצי DEX, הם מבצעים תהליך מורכב של קבלת החלטות כדי לקבוע אילו מחלקות נדרשות בקובץ ה-DEX הראשי, כדי שהאפליקציה תוכל להתחיל לפעול בהצלחה. אם לא מספקים בקובץ ה-DEX הראשי מחלקה שנדרשת במהלך ההפעלה, האפליקציה קורסת עם השגיאה java.lang.NoClassDefFoundError.
כלי הבנייה מזהים את נתיבי הקוד של קוד שאליו יש גישה ישירה מקוד האפליקציה. עם זאת, הבעיה הזו יכולה להתרחש כשנתיבי הקוד פחות גלויים, למשל כשספרייה שבה אתם משתמשים כוללת תלויות מורכבות. לדוגמה, אם הקוד משתמש באינטרוספקציה או בהפעלת שיטות 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 בגרסאות פיתוח
הגדרת multidex דורשת זמן עיבוד ארוך יותר באופן משמעותי, כי מערכת ה-build צריכה לקבל החלטות מורכבות לגבי המחלקות שצריך לכלול בקובץ ה-DEX הראשי והמחלקות שאפשר לכלול בקובצי DEX משניים. המשמעות היא שבדרך כלל, בנייה מצטברת באמצעות multidex אורכת זמן רב יותר ועלולה להאט את תהליך הפיתוח.
כדי לקצר את משך הזמן של גרסאות build מצטברות, אפשר להשתמש בpre-dexing כדי לעשות שימוש חוזר בפלט של multidex בין גרסאות build.
השימוש ב-pre-dexing מסתמך על פורמט ART שזמין רק ב-Android 5.0 (רמת API 21) ומעלה. אם אתם משתמשים ב-Android Studio, סביבת הפיתוח המשולבת (IDE) משתמשת אוטומטית ב-pre-dexing כשפורסים את האפליקציה במכשיר עם Android בגרסה 5.0 (רמת API 21) ומעלה.
עם זאת, אם אתם מריצים Gradle builds משורת הפקודה, אתם צריכים להגדיר את 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 מ-Android Studio או משורת הפקודה, אפשר לעיין במאמר אופטימיזציה של מהירות ה-build. מידע נוסף על שימוש בווריאציות של build זמין במאמר הגדרת וריאציות של build.
טיפ: אם יש לכם וריאציות שונות של build לצרכים שונים של multidex, אתם יכולים לספק קובץ מניפסט שונה לכל וריאציה, כך שרק הקובץ לרמת 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); ... }