فعال کردن 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 (DEX) فراخوانی کند. این صفحه توضیح می‌دهد که چگونه با فعال کردن پیکربندی برنامه که به‌عنوان multidex شناخته می‌شود، از این محدودیت عبور کنید. این پیکربندی به برنامه شما امکان می‌دهد چندین فایل DEX را بسازد و بخواند.

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

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

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

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

نسخه‌های پلاتفرم قبل‌از Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) از زمان اجرای Dalvik برای اجرای کد برنامه استفاده می‌کنند. به‌طور پیش‌فرض، Dalvik برنامه‌ها را به یک فایل classes.dex کدبایت در هر APK محدود می‌کند. برای دور زدن این محدودیت، کتابخانه multidex را به فایل 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 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر از زمان اجرایی به نام ART استفاده می‌کند که به‌طور بومی از بار کردن چندین فایل DEX از فایل‌های APK پشتیبانی می‌کند. ‫ART در زمان نصب برنامه پیش‌ترجمه انجام می‌دهد و فایل‌های classesN.dex را اسکن می‌کند و آن‌ها را در یک فایل OAT برای اجرا توسط دستگاه Android ترجمه می‌کند. بنابراین، اگر minSdkVersion شما ۲۱ یا بالاتر باشد، مولتی‌دکس به‌طور پیش‌فرض فعال است و به کتابخانه مولتی‌دکس نیاز ندارید.

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

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

از محدودیت ۶۴ هزارتایی اجتناب کنید

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

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

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

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

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

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

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

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

    شیک

    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) را برای فعال کردن multidex فراخوانی کنید:

      کاتلین

      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()، MultiDex.install() یا هر کد دیگری را ازطریق انعکاس یا JNI اجرا نکنید. ردیابی Multidex آن فراخوانی‌ها را دنبال نخواهد کرد، که این امر باعث بروز خطاهای ClassNotFoundException یا درستی‌سنجی به‌دلیل پارتیشن کلاس بد بین فایل‌های DEX می‌شود.

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

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

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

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

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

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

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

کلاس‌های موردنیاز را در فایل 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")
            ...
        }
    }
}

بهینه‌سازی multidex در ساخت‌های توسعه

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

برای کاهش زمان‌های ساخت افزایشی طولانی‌تر، از پیش‌دکسینگ برای استفاده مجدد از برونداد چنددکس بین ساخت‌ها استفاده کنید. پیش‌دکسینگ به قالب ART متکی است که فقط در Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر دردسترس است. اگر از Android Studio استفاده می‌کنید، IDE به‌طور خودکار هنگام استقرار برنامه در دستگاهی که Android 5.0 (سطح API 21) یا بالاتر را اجرا می‌کند، از پیش‌دکسینگ استفاده می‌کند. بااین‌حال، اگر درحال اجرای ساخت‌های 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 Studio یا خط فرمان، بهینه‌سازی سرعت ساخت را بخوانید. برای اطلاعات بیشتر درباره استفاده از گونه‌های ساخت، به پیکربندی گونه‌های ساخت مراجعه کنید.

نکته: اگر برای نیازهای مختلف multidex، گونه‌های ساخت مختلفی دارید، می‌توانید برای هر گونه فایل مانیفست متفاوتی ارائه دهید تا فقط فایل مربوط به سطح API ۲۰ و پایین‌تر نام برچسب <application> را تغییر دهد. همچنین می‌توانید برای هر گونه، زیرکلاس Application متفاوتی ایجاد کنید تا فقط زیرکلاس سطح API 20 و پایین‌تر کلاس 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);
  ...
}