אם באפליקציה שלכם 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. המספר הזה מייצג את המספר הכולל של ההפניות שאפשר להפעיל באמצעות הקוד בקובץ בייטקוד אחד של Dalvik Executable (DEX). בדף הזה מוסבר איך לעקוף את המגבלה הזו על ידי הפעלת הגדרת אפליקציה שנקראת multidex, שמאפשרת לאפליקציה ליצור ולקרוא כמה קובצי DEX.
מידע על מגבלת ההפניה של 64K
קבצים של אפליקציות ל-Android (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
bytecode לכל 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 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
class אבל אי אפשר לשנות את מחלקת הבסיס, אתם צריכים לבטל את ההגדרה של השיטה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. כשמשלבים את הספרייה בהגדרת בניית האפליקציה, כדאי לקחת בחשבון את הנקודות הבאות:
- ההתקנה של קובצי DEX במהלך ההפעלה במחיצת הנתונים של המכשיר היא מורכבת, ויכולה לגרום לשגיאות מסוג ANR (שגיאות שבהן האפליקציה לא מגיבה) אם קובצי ה-DEX המשניים גדולים. כדי להימנע מהבעיה הזו, מפעילים כיווץ קוד כדי למזער את הגודל של קובצי DEX ולהסיר חלקים בקוד שלא נמצאים בשימוש.
- כשמריצים בגרסאות קודמות ל-Android 5.0 (רמת API 21), שימוש ב-multidex לא מספיק כדי לעקוף את המגבלה של linearalloc (בעיה מספר 37008143). המגבלה הזו הוגדלה ב-Android 4.0 (רמת API 14), אבל זה לא פתר את הבעיה לחלוטין.
בגרסאות שקדמו ל-Android 4.0, יכול להיות שתגיעו למגבלת ה-linearalloc לפני שתגיעו למגבלת אינדקס ה-DEX. לכן, אם אתם מטargetים רמות API נמוכות מ-14, חשוב לבצע בדיקות יסודיות בגרסאות האלה של הפלטפורמה, כי יכול להיות שיהיו בעיות באפליקציה בזמן ההפעלה או כשקבוצות מסוימות של מחלקות נטענות.
הקטנת קוד יכולה לצמצם את הבעיות האלה או אפילו למנוע אותן.
הצהרה על מחלקות שנדרשות בקובץ ה-DEX הראשי
כשכלי הבנייה יוצרים כל קובץ DEX לאפליקציה עם כמה קובצי DEX, הם מבצעים תהליך מורכב של קבלת החלטות כדי לקבוע אילו מחלקות נדרשות בקובץ ה-DEX הראשי, כך שהאפליקציה תוכל להתחיל לפעול בהצלחה. אם לא מספקים בקובץ ה-DEX הראשי מחלקה שנדרשת במהלך ההפעלה, האפליקציה קורסת עם השגיאה java.lang.NoClassDefFoundError
.
כלי הבנייה מזהים את נתיבי הקוד של קוד שאליו יש גישה ישירה מקוד האפליקציה. עם זאת, הבעיה הזו יכולה להתרחש כשנתיבי הקוד פחות גלויים, למשל כשספרייה שבה אתם משתמשים כוללת תלות מורכבת. לדוגמה, אם הקוד משתמש באינטרוספקציה או בהפעלת שיטות 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 בגרסאות פיתוח
הגדרת multidex דורשת זמן עיבוד ארוך יותר באופן משמעותי, כי מערכת ה-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) ומעלה.
עם זאת, אם אתם מריצים קובצי Gradle build משורת הפקודה, אתם צריכים להגדיר את הערך של 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") }
כדי לקרוא על אסטרטגיות נוספות לשיפור מהירויות הבנייה מ-Android Studio או משורת הפקודה, אפשר לעיין במאמר אופטימיזציה של מהירות הבנייה. מידע נוסף על השימוש בווריאציות של בנייה זמין במאמר הגדרת וריאציות של בנייה.
טיפ: אם יש לכם וריאציות שונות של build לצרכים שונים של multidex, אתם יכולים לספק קובץ מניפסט שונה לכל וריאציה, כך שרק הקובץ לרמה 20 של API ומטה ישנה את שם התג <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); ... }