אם באפליקציה שלכם יש 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. המספר הזה מייצג את המספר הכולל של ההפניות שאפשר להפעיל באמצעות הקוד בקובץ bytecode יחיד של Dalvik Executable (DEX). בדף הזה מוסבר איך לעקוף את המגבלה הזו על ידי הפעלת הגדרה של אפליקציה שנקראת multidex, שמאפשרת לאפליקציה ליצור ולקרוא קובצי DEX מרובים.
מידע על מגבלת 64K של קובצי העזר
קובצי אפליקציות Android (APK) מכילים קובצי bytecode להפעלה בצורת קובצי Dalvik Executable (DEX), שמכילים את הקוד המהדר שמשמש להפעלת האפליקציה. מפרט Dalvik Executable מגביל את המספר הכולל של השיטות שאפשר להפנות אליהן בקובץ DEX יחיד ל-65,536, כולל שיטות של מסגרת Android, שיטות של ספריות ושיטות בקוד שלכם.
בהקשר של מדעי המחשב, המונח kilo או K מציין את הערך 1024 (או 2^10). מכיוון ש-65,536 שווה ל-64x1,024, המגבלה הזו נקראת _64K Reference limit_.תמיכה ב-Multidex לפני Android 5.0
בגרסאות של הפלטפורמה שקדמו ל-Android 5.0 (רמת API 21), סביבת זמן הריצה של Dalvik משמשת להרצת קוד האפליקציה. כברירת מחדל, Dalvik מגביל אפליקציות לקובץ bytecode יחיד של classes.dex
לכל קובץ APK. כדי לעקוף את המגבלה הזו, מוסיפים את ספריית multidex לקובץ build.gradle
או build.gradle.kts
ברמת המודול:
Groovy
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 ל-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) ו-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 כיחס תלות, כפי שמוצג כאן:Groovy
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 33 multiDexEnabled true } ... } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... minSdk = 15 targetSdk = 33 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 משתמשים בתוכנית טעינה מיוחדת של כיתות כדי לחפש את השיטות שלכם בכל קובצי ה-DEX הזמינים.
המגבלות של ספריית multidex
לספריית multidex יש כמה מגבלות ידועות. כשאתם משלבים את הספרייה בהגדרות ה-build של האפליקציה, כדאי לשקול את הדברים הבאים:
- התקנת קובצי DEX במהלך ההפעלה במחיצה של נתוני המכשיר היא מורכבת, ועלולה לגרום לשגיאות מסוג Application Not Responding (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, כלי ה-build מבצעים תהליך קבלת החלטות מורכב כדי לקבוע אילו כיתות נדרשות בקובץ ה-DEX הראשי כדי שהאפליקציה תוכל להתחיל לפעול. אם כיתה שנדרשת במהלך ההפעלה לא מסופקת בקובץ ה-DEX הראשי, האפליקציה תקרוס עם השגיאה java.lang.NoClassDefFoundError
.
כלי ה-build מזהים את נתיבי הקוד של קוד שגישה אליו מתבצעת ישירות מקוד האפליקציה. אבל הבעיה הזו עשויה להתרחש כשנתיבי הקוד פחות גלויים, למשל כשספרייה שבה משתמשים כוללת יחסי תלות מורכבים. לדוגמה, אם הקוד משתמש בבדיקה עצמית או בהפעלה של שיטות Java מקוד מקורי, יכול להיות שהמערכות לא יזהו את הכיתות האלה כנדרש בקובץ ה-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 ב-builds של פיתוח
הגדרת multidex מחייבת זמן עיבוד ארוך יותר של ה-build, כי מערכת ה-build צריכה לקבל החלטות מורכבות לגבי הכיתות שצריך לכלול בקובץ ה-DEX הראשי ואיזה כיתות אפשר לכלול בקובצי ה-DEX המשניים. המשמעות היא ש-build מצטבר באמצעות multidex נמשך בדרך כלל זמן רב יותר, ויכול להאט את תהליך הפיתוח.
כדי לצמצם את זמני ה-build המצטברים הארוכים, כדאי להשתמש ביצירת קידוד מקדים (pre-dexing) כדי לעשות שימוש חוזר בפלט מסוג multidex בין גרסאות build.
דחיסה מראש מבוססת על פורמט ART שזמין רק ב-Android 5.0 (רמת API 21) ואילך. אם אתם משתמשים ב-Android Studio, סביבת הפיתוח המשולבת (IDE) משתמשת באופן אוטומטי ב-dexing מראש כשפורסים את האפליקציה במכשיר עם Android 5.0 (רמת API 21) ומעלה.
עם זאת, אם מריצים את גרסת ה-build של Gradle משורת הפקודה, צריך להגדיר את minSdkVersion
לערך 21 ואילך כדי לאפשר יצירת קידוד מראש.
minSdkVersion
, כפי שמוצג בדוגמה:
Groovy
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, אפשר לספק קובץ מניפסט שונה לכל
וריאציה, כך שרק הקובץ ברמת API 20 ומטה ישנה את
שם התג <application>
. אפשר גם ליצור לכל וריאנט תת-סוג Application
שונה, כך שרק תת-הסוג של רמת ה-API 20 ומטה ירחיב את הכיתה MultiDexApplication
או יבצע קריאה ל-MultiDex.install(this)
.
בדיקת אפליקציות multidex
כשכותבים בדיקות אינסטרומנטציה לאפליקציות multidex, אין צורך בהגדרות נוספות אם משתמשים באינסטרומנטציה
MonitoringInstrumentation
או
AndroidJUnitRunner
. אם משתמשים ב-Instrumentation
אחר, צריך לשנות את ה-method onCreate()
שלו באמצעות הקוד הבא:
Kotlin
fun onCreate(arguments: Bundle) { MultiDex.install(targetContext) super.onCreate(arguments) ... }
Java
public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... }