فعال کردن multidex برای برنامه‌های دارای بیش‌از ۶۴ هزار روش

اگر برنامه شما دارای minSdk از API 20 یا پایین‌تر باشد و برنامه شما و کتابخانه‌هایی که به آن‌ها ارجاع می‌دهد از ۶۵,۵۳۶ روش فراتر بروند، با خطای ساخت زیر مواجه می‌شوید که نشان می‌دهد برنامه شما به حد مجاز معماری ساخت Android رسیده است:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

نسخه‌های قدیمی‌تر سیستم ساخت خطای متفاوتی را گزارش می‌کنند که نشان‌دهنده همان مشکل است:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

این شرایط خطا عدد مشترکی را نمایش می‌دهند: ۶۵۵۳۶. این عدد نشان‌دهنده تعداد کل ارجاعاتی است که می‌تواند توسط کد در یک فایل بایت‌کد Dalvik Executable (DEX)‎ فراخوانی شود. این صفحه توضیح می‌دهد که چگونه با فعال کردن پیکربندی برنامه موسوم به مولتی‌دکس، که به برنامه شما اجازه می‌دهد چندین فایل DEX بسازد و بخواند، از این محدودیت عبور کنید.

درباره حد مرجع ۶۴ هزار

فایل‌های برنامه Android‏ (APK) حاوی فایل‌های بایت‌کد اجرایی به‌شکل فایل‌های Dalvik Executable (DEX) هستند که حاوی کد کامپایل‌شده مورداستفاده برای اجرای برنامه شما است. مشخصات Dalvik Executable تعداد کل روش‌هایی را که می‌توان در یک فایل DEX به آن‌ها ارجاع داد به ۶۵٬۵۳۶ محدود می‌کند—ازجمله روش‌های چارچوب Android، روش‌های کتابخانه، و روش‌های موجود در کد خودتان.

در زمینه علوم رایانه، اصطلاح کیلو، یا K، نشان‌دهنده ۱۰۲۴ (یا ۲^۱۰) است. چون ۶۵٬۵۳۶ برابر با ۶۴x۱۰۲۴ است، این محدودیت به‌عنوان _محدودیت مرجع ۶۴K_ شناخته می‌شود.

پشتیبانی از مولتی‌دکس قبل‌از Android 5.0

نسخه‌های پلاتفرم قبل‌از Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) از زمان اجرای Dalvik برای اجرای کد برنامه استفاده می‌کنند. به‌طور پیش‌فرض، Dalvik برنامه‌ها را به یک فایل بایت‌کد classes.dex برای هر APK محدود می‌کند. برای دور زدن این محدودیت، کتابخانه مولتی‌دکس را به فایل build.gradle یا build.gradle.kts در سطح ماژول اضافه کنید:

شیک

dependencies {
    def multidex_version = "2.0.1"
    implementation "androidx.multidex:multidex:$multidex_version"
}

کاتلین

dependencies {
    val multidex_version = "2.0.1"
    implementation("androidx.multidex:multidex:$multidex_version")
}

این کتابخانه بخشی از فایل DEX اصلی برنامه‌تان می‌شود و سپس دسترسی به فایل‌های DEX اضافی و کد موجود در آن‌ها را مدیریت می‌کند. برای مشاهده نسخه‌های فعلی این کتابخانه، نسخه‌های multidex را ببینید.

برای جزئیات بیشتر، به بخش مربوط به نحوه پیکربندی برنامه برای مولتی‌دکس مراجعه کنید.

پشتیبانی از Multidex برای Android 5.0 و بالاتر

Android نسخه ۵.۰ (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر از زمان اجرایی به‌نام ART استفاده می‌کند که به‌طور بومی از بار کردن چند فایل DEX از فایل‌های APK پشتیبانی می‌کند. ART پیش‌کامپایل را در زمان نصب برنامه انجام می‌دهد، فایل‌های classesN.dex را اسکن می‌کند و آن‌ها را در یک فایل OAT واحد برای اجرا توسط دستگاه Android کامپایل می‌کند. بنابراین، اگر minSdkVersion شما ۲۱ یا بالاتر باشد، مولتی‌دکس به‌طور پیش‌فرض فعال است و به کتابخانه مولتی‌دکس نیاز ندارید.

برای اطلاعات بیشتر درباره زمان اجرای Android 5.0، زمان اجرای Android (ART) و Dalvik را بخوانید.

توجه: هنگام اجرای برنامه بااستفاده از Android Studio، ساخت برای دستگاه‌های هدفی که در آن‌ها مستقر می‌کنید بهینه‌سازی می‌شود. این شامل فعال کردن مولتی‌دکس در زمانی است که دستگاه‌های هدف از Android 5.0 و بالاتر استفاده می‌کنند. ازآنجایی‌که این بهینه‌سازی فقط هنگام استقرار برنامه بااستفاده از «استودیو Android» اعمال می‌شود، ممکن است همچنان لازم باشد ساخت نسخه پخش خود را برای مولتی‌دکس پیکربندی کنید تا از محدودیت ۶۴K جلوگیری شود.

اجتناب از محدودیت 64K

قبل‌از پیکربندی برنامه‌تان برای فعال کردن استفاده از ۶۴ هزار یا بیشتر مرجع روش، اقداماتی انجام دهید تا تعداد کل مراجع فراخوانی‌شده توسط کد برنامه‌تان کاهش یابد، ازجمله روش‌های تعریف‌شده توسط کد برنامه‌تان یا کتابخانه‌های گنجانده‌شده.

استراتژی‌های زیر می‌تواند به شما کمک کند تا به حد مرجع DEX نرسید:

بررسی وابستگی‌های مستقیم و انتقالی برنامه
بررسی کنید که آیا ارزش هر وابستگی کتابخانه بزرگی که در برنامه‌تان اضافه می‌کنید بیشتر از مقدار کدی است که به برنامه اضافه می‌شود. یک الگوی رایج اما مشکل‌ساز این است که یک کتابخانه بسیار بزرگ را اضافه کنید چون چند روش کاربردی مفید بوده‌اند. کاهش وابستگی‌های کد برنامه اغلب می‌تواند به شما کمک کند از حد مرجع DEX اجتناب کنید.
برداشتن کد استفاده‌نشده با R8
کوچک کردن کد را فعال کنید تا R8 برای ساخت‌های نسخه پخش شما اجرا شود. کوچک کردن را فعال کنید تا مطمئن شوید که کد استفاده‌نشده را با APK خود ارسال نمی‌کنید. اگر کوچک کردن کد به‌درستی پیکربندی شده باشد، می‌تواند کدها و منابع استفاده‌نشده را نیز از وابستگی‌های شما حذف کند.

استفاده از این تکنیک‌ها می‌تواند به شما کمک کند اندازه کلی APK خود را کاهش دهید و از نیاز به مولتی‌دکس در برنامه خود جلوگیری کنید.

پیکربندی برنامه برای مولتی‌دکس

توجه: اگر minSdkVersion روی ۲۱ یا بالاتر تنظیم شده باشد، multidex به‌طور پیش‌فرض فعال است و به کتابخانه multidex نیاز ندارید.

اگر minSdkVersion شما روی ۲۰ یا کمتر تنظیم شده است، باید از کتابخانه مولتی‌دکس استفاده کنید و اصلاحات زیر را در پروژه برنامه‌تان انجام دهید:

  1. فایل build.gradle در سطح ماژول را اصلاح کنید تا مولتی‌دکس فعال شود و کتابخانه مولتی‌دکس به‌عنوان وابستگی اضافه شود، همان‌طور که در اینجا نشان داده شده است:

    شیک

    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 36
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }

    کاتلین

    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 36
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
  2. بسته به اینکه کلاس 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 را به‌صورت زیر گسترش دهد:

      کاتلین

      class MyApplication : MultiDexApplication() {...}

      جاوا

      public class MyApplication extends MultiDexApplication { ... }
    • اگر کلاس Application را لغو می‌کنید اما امکان تغییر کلاس پایه وجود ندارد، به‌جای آن روش attachBaseContext() را لغو کنید و MultiDex.install(this) را برای فعال کردن مولتی‌دکس فراخوانی کنید:

      کاتلین

      class MyApplication : SomeOtherApplication() {
      
          override fun attachBaseContext(base: Context) {
              super.attachBaseContext(base)
              MultiDex.install(this)
          }
      }

      جاوا

      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 می‌شود.

اکنون وقتی برنامه‌تان را می‌سازید، ابزارهای ساخت Android یک فایل DEX اصلی (classes.dex) و فایل‌های DEX پشتیبانی (classes2.dex، classes3.dex، و غیره) را درصورت نیاز می‌سازند. سپس سیستم ساخت همه فایل‌های DEX را در APK شما بسته‌بندی می‌کند.

در زمان اجرا، به‌جای جستجو فقط در فایل اصلی classes.dex، میاناهای برنامه‌سازی کاربردی مولتی‌دکس از یک بارکننده کلاس ویژه برای جستجو در همه فایل‌های DEX دردسترس برای روش‌های شما استفاده می‌کنند.

محدودیت‌های کتابخانه مولتی‌دکس

کتابخانه مولتی‌دکس محدودیت‌های شناخته‌شده‌ای دارد. وقتی کتابخانه را در پیکربندی ساخت برنامه‌تان ادغام می‌کنید، موارد زیر را درنظر بگیرید:

  • نصب فایل‌های DEX درطول راه‌اندازی در بخش داده دستگاه پیچیده است و اگر فایل‌های DEX ثانویه بزرگ باشند، می‌تواند منجر به خطاهای «برنامه پاسخ نمی‌دهد» (ANR) شود. برای اجتناب از این مشکل، کوچک کردن کد را فعال کنید تا اندازه فایل‌های DEX را به‌حداقل برسانید و بخش‌های استفاده‌نشده کد را بردارید.
  • هنگام اجرا در نسخه‌های قبل‌از Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱)، استفاده از مولتی‌دکس برای حل مشکل محدودیت linearalloc کافی نیست (مشکل ۳۷۰۰۸۱۴۳). این محدودیت در Android 4.0 (سطح میانای برنامه‌سازی کاربردی ۱۴) افزایش یافت، اما مشکل را کاملاً حل نکرد.

    در نسخه‌های قدیمی‌تر از Android 4.0، ممکن است قبل‌از رسیدن به حد شاخص DEX، به حد linearalloc برسید. بنابراین اگر سطح میانای برنامه‌سازی کاربردی هدف شما پایین‌تر از ۱۴ است، برنامه را به‌طور کامل در آن نسخه‌های پلاتفرم آزمایش کنید، زیرا ممکن است برنامه‌تان در زمان راه‌اندازی یا هنگام بار شدن گروه‌های خاصی از کلاس‌ها با مشکل مواجه شود.

    کوچک کردن کد می‌تواند این مشکلات را کاهش دهد یا احتمالاً برطرف کند.

کلاس‌های موردنیاز را در فایل DEX اصلی اعلام کنید

هنگام ساختن هر فایل DEX برای برنامه مولتی‌دکس، ابزارهای ساخت تصمیم‌گیری‌های پیچیده‌ای انجام می‌دهند تا مشخص کنند کدام کلاس‌ها در فایل DEX اصلی موردنیاز هستند تا برنامه شما بتواند با موفقیت شروع شود. اگر هر کلاسی که درطول راه‌اندازی لازم است در فایل DEX اصلی ارائه نشود، برنامه‌تان با خطای java.lang.NoClassDefFoundError ازکار می‌افتد.

ابزارهای ساخت، مسیرهای کد را برای کدی که مستقیماً از کد برنامه شما قابل‌دسترسی است تشخیص می‌دهند. بااین‌حال، این مشکل می‌تواند زمانی رخ دهد که مسیرهای کد کمتر قابل‌مشاهده باشند، مثلاً زمانی که کتابخانه‌ای که استفاده می‌کنید وابستگی‌های پیچیده‌ای دارد. برای مثال، اگر کد از درون‌نگری یا فراخوانی روش‌های Java از کد بومی استفاده کند، ممکن است آن کلاس‌ها به‌عنوان کلاس‌های موردنیاز در فایل DEX اصلی شناسایی نشوند.

اگر java.lang.NoClassDefFoundError را دریافت کردید، باید کلاس‌های اضافی موردنیاز در فایل DEX اصلی را با اعلام آن‌ها با ویژگی multiDexKeepProguard در نوع ساخت خود به‌صورت دستی مشخص کنید. اگر کلاسی در فایل 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

سپس می‌توانید آن فایل را برای نوع ساخت به‌صورت زیر اعلام کنید:

شیک

android {
    buildTypes {
        release {
            multiDexKeepProguard file('multidex-config.pro')
            ...
        }
    }
}

کاتلین

android {
    buildTypes {
        getByName("release") {
            multiDexKeepProguard = file("multidex-config.pro")
            ...
        }
    }
}

بهینه‌سازی مولتی‌دکس در نسخه‌های توسعه

پیکربندی مولتی‌دکس به زمان پردازش ساخت بسیار بیشتری نیاز دارد زیرا سیستم ساخت باید تصمیمات پیچیده‌ای درباره اینکه کدام کلاس‌ها باید در فایل DEX اصلی گنجانده شوند و کدام کلاس‌ها می‌توانند در فایل‌های DEX فرعی گنجانده شوند بگیرد. این یعنی ساخت‌های افزایشی بااستفاده از مولتی‌دکس معمولاً بیشتر طول می‌کشد و می‌تواند فرایند توسعه شما را کند کند.

برای کاهش زمان‌های ساخت افزایشی طولانی‌تر، از ایجاد dex اولیه برای استفاده مجدد از خروجی مولتی‌دکس بین ساخت‌ها استفاده کنید. ایجاد dex اولیه به قالب ART متکی است که فقط در Android نسخه ۵.۰ (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر دردسترس است. اگر از Android Studio استفاده می‌کنید، IDE به‌طور خودکار از ایجاد dex اولیه هنگام استقرار برنامه‌تان در دستگاهی با Android نسخه ۵.۰ (سطح میانای برنامه‌سازی کاربردی ۲۱) یا بالاتر استفاده می‌کند. بااین‌حال، اگر درحال اجرای ساخت‌های Gradle از خط فرمان هستید، باید minSdkVersion را روی ۲۱ یا بالاتر تنظیم کنید تا ایجاد dex اولیه فعال شود.

برای حفظ تنظیمات مربوط به ساخت تولید، می‌توانید دو نسخه از برنامه‌تان را بااستفاده از نسخه‌های ویژه محصول ایجاد کنید—یک نسخه با نسخه ویژه توسعه و یک نسخه با نسخه ویژه انتشار—با مقادیر متفاوت برای 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"
}

کاتلین

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» یا خط فرمان، بهینه‌سازی سرعت ساخت را بخوانید. برای اطلاعات بیشتر درباره استفاده از تنوع‌های ساخت، پیکربندی تنوع‌های ساخت را ببینید.

نکته: اگر تنوع‌های ساخت متفاوتی برای نیازهای مختلف multidex دارید، می‌توانید فایل مانیفست متفاوتی برای هر تنوع ارائه دهید تا فقط فایل مربوط به سطح API‏ ۲۰ و پایین‌تر نام برچسب <application> را تغییر دهد. همچنین می‌توانید برای هر گونه، زیرکلاس Application متفاوتی ایجاد کنید تا فقط زیرکلاس سطح میانای برنامه کاربردی ۲۰ و پایین‌تر کلاس MultiDexApplication را گسترش دهد یا MultiDex.install(this) را فراخوانی کند.

آزمایش برنامه‌های مولتی‌دکس

وقتی برای برنامه‌های مولتی‌دکس تست‌های ابزار دقیق می‌نویسید، اگر از ابزار دقیق MonitoringInstrumentation یا AndroidJUnitRunner استفاده کنید، به پیکربندی اضافی نیاز نیست. اگر از Instrumentation دیگری استفاده می‌کنید، باید روش onCreate() آن را با کد زیر لغو کنید:

کاتلین

fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}

جاوا

public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}