הפעלת Multidex באפליקציות עם יותר מ-64K שיטות

אם לאפליקציה יש minSdk של API 20 ומטה וגם לאפליקציה הספריות שהיא מפנה אליה חורגות מ-65,536 methods, ומקבלים את שגיאת ה-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) מכילים קובצי בייטקוד להפעלה בצורה מדאלוויק קובצי הפעלה (DEX), שמכילים את הקוד שעבר הידור שמשמש להפעלת האפליקציה. מפרט Dalvik Executable מגביל את המספר הכולל של השיטות ניתן להפנות לקובץ DEX יחיד עד 65,536 — כולל Android methods, שיטות ספריות ו-methods בקוד שלכם.

ב בהקשר של מדעי המחשב, המונח kilo, או K מציין את שנת 1024 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 file:

מגניב

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) ו-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 את השינויים הבאים בפרויקט האפליקציה שלכם:

  1. שינוי הקובץ 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")
    }
    
  2. אם משנים את הערך בשדה Application, class, מבצעים אחת מהפעולות הבאות:
    • אם לא משנים את Application class, לערוך את קובץ המניפסט כדי להגדיר את 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 טעינה מיוחד כדי לחפש בכל קובצי DEX שזמינים לשיטות שלכם.

המגבלות של ספריית multidex

לספריית Multidex יש כמה מגבלות ידועות. כשמשלבים את הספרייה בהגדרת ה-build של האפליקציה, כדאי להביא בחשבון את הנקודות הבאות:

  • ההתקנה של קובצי DEX במהלך ההפעלה במחיצת הנתונים של המכשיר היא מורכבת יכולים לגרום לשגיאות של האפליקציה לא מגיבה (ANR) אם קובצי ה-DEX המשניים גדולים. שפת תרגום להימנע מהבעיה הזו, עליכם להפעיל את כיווץ הקוד כדי לצמצם את הגודל של קובצי ה-DEX ולהסיר חלקי קוד שלא נמצאים בשימוש.
  • כשמפעילים גרסאות שקודמות ל-Android 5.0 (רמת API 21), באמצעות Multidex אינו מספיק כדי לעקוף את המגבלה ליניארית (בעיה 37008143). מגבלה זו הוגדלה ב Android 4.0 (רמת API 14), אבל הבעיה לא נפתרה לגמרי.

    בגרסאות שקודמות ל-Android 4.0, ייתכן שתגיעו למגבלת הלינארית לפני להגיע למגבלה של אינדקס ה-DEX. לכן אם אתם מטרגטים רמות API נמוכות מ- 14, לבצע בדיקה יסודית בגרסאות האלה של הפלטפורמה, כי האפליקציה לבעיות בהפעלה או כאשר קבוצות מסוימות של כיתות נטענות.

    כיווץ הקוד עלול לצמצם או ואולי לפתור את הבעיות האלה.

צריך להצהיר על כיתות בקובץ ה-DEX הראשי

כשיוצרים כל קובץ DEX לאפליקציה multidex, כלי ה-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

אחר כך אפשר להצהיר (declare) על הקובץ הזה לסוג 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. לפני הוספת ה-dexing נעשה שימוש בפורמט ART שזמין רק ב-Android 5.0 (API ברמה 21) ומעלה. אם משתמשים ב-Android Studio, סביבת הפיתוח המשולבת (IDE) משתמשת באופן אוטומטי בתהליך הפיתוח (pre-dexing) כשפורסים את האפליקציה במכשיר שמותקנת בו גרסת Android 5.0 (רמת API 21) ומעלה. עם זאת, אם מריצים את גרסת ה-build של Gradle משורת הפקודה, צריך להגדיר minSdkVersion עד 21 ואילך כדי לאפשר הוספה של קטעים מראש.

כדי לשמור על ההגדרות של לסביבת הייצור, אפשר ליצור שתי גרסאות של האפליקציה שימוש בטעמים של מוצרים – גרסה אחת עם טעם פיתוח וגרסה אחת עם טעם חדש של השקה, ערכים שונים עבור 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 שונות לגרסאות 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);
  ...
}