Multidex را برای برنامه هایی با روش های بیش از 64K فعال کنید

اگر برنامه شما دارای minSdk با API 20 یا کمتر باشد و برنامه و کتابخانه‌هایی که به آنها ارجاع می‌دهد بیش از 65536 متد داشته باشند، با خطای ساخت زیر مواجه می‌شوید که نشان می‌دهد برنامه شما به حد معماری ساخت اندروید رسیده است:

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

درباره محدودیت مرجع ۶۴ کیلوبایتی

فایل‌های برنامه اندروید (APK) حاوی فایل‌های بایت‌کد اجرایی به شکل فایل‌های Dalvik Executable (DEX) هستند که شامل کد کامپایل شده مورد استفاده برای اجرای برنامه شما هستند. مشخصات Dalvik Executable تعداد کل متدهایی را که می‌توان در یک فایل DEX به آنها ارجاع داد به 65536 محدود می‌کند - از جمله متدهای چارچوب اندروید، متدهای کتابخانه و متدهای موجود در کد خودتان.

در زمینه علوم کامپیوتر، اصطلاح کیلو یا K به معنای 1024 (یا 2^10) است. از آنجا که 65536 برابر با 64x1024 است، به این حد، حد مرجع _64K_ گفته می‌شود.

پشتیبانی از Multidex قبل از اندروید ۵.۰

نسخه‌های قبل از اندروید ۵.۰ (سطح API ۲۱) از Dalvik runtime برای اجرای کد برنامه استفاده می‌کنند. به طور پیش‌فرض، 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 مراجعه کنید.

پشتیبانی از Multidex برای اندروید ۵.۰ و بالاتر

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

برای اطلاعات بیشتر در مورد زمان اجرای اندروید ۵.۰، Android Runtime (ART) و Dalvik را مطالعه کنید.

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

از محدودیت ۶۴ کیلوبایت عبور کنید

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

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

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

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

برنامه خود را برای multidex پیکربندی کنید

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

اگر minSdkVersion شما روی ۲۰ یا کمتر تنظیم شده است، باید از کتابخانه multidex استفاده کنید و تغییرات زیر را در پروژه برنامه خود اعمال کنید:

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

    گرووی

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

    کاتلین

    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 33
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
  2. بسته به اینکه آیا کلاس Application را override می‌کنید یا خیر، یکی از موارد زیر را انجام دهید:
    • اگر کلاس Application را override نمی‌کنید، فایل manifest خود را ویرایش کنید تا 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 را override می‌کنید، آن را به صورت زیر به ارث بری از MultiDexApplication تغییر دهید:

      کاتلین

      class MyApplication : MultiDexApplication() {...}

      جاوا

      public class MyApplication extends MultiDexApplication { ... }
    • اگر کلاس Application را override می‌کنید اما امکان تغییر کلاس پایه وجود ندارد، در عوض متد attachBaseContext() را override کرده و 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() ‎ یا هر کد دیگری را از طریق reflection یا JNI اجرا نکنید. ردیابی Multidex این فراخوانی‌ها را دنبال نمی‌کند و باعث ایجاد ClassNotFoundException یا خطاهای تأیید به دلیل پارتیشن‌بندی کلاس نامناسب بین فایل‌های DEX می‌شود.

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

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

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

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

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

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

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

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

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

ابزارهای ساخت، مسیرهای کد را برای کدی که مستقیماً از کد برنامه شما قابل دسترسی است، تشخیص می‌دهند. با این حال، این مشکل می‌تواند زمانی رخ دهد که مسیرهای کد کمتر قابل مشاهده باشند، مانند زمانی که کتابخانه‌ای که استفاده می‌کنید وابستگی‌های پیچیده‌ای دارد. به عنوان مثال، اگر کد از درون‌نگری یا فراخوانی متدهای جاوا از کد بومی استفاده کند، ممکن است آن کلاس‌ها به عنوان مورد نیاز در فایل 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 در نسخه‌های در حال توسعه

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

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

برای حفظ تنظیمات برای نسخه نهایی، می‌توانید دو نسخه از برنامه خود را با استفاده از product flavors ایجاد کنید - یک نسخه با development flavor و یک نسخه با release flavor - با مقادیر مختلف برای 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")
}

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

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

تست برنامه‌های چندرسانه‌ای

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

کاتلین

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

جاوا

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