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

اگر برنامه شما دارای minSdk API 20 یا پایین‌تر باشد و برنامه شما و کتابخانه‌هایی که به آن ارجاع می‌دهد بیش از 65536 روش باشد، با خطای ساخت زیر مواجه می‌شوید که نشان می‌دهد برنامه شما به محدودیت معماری ساخت 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

این شرایط خطا یک عدد مشترک را نشان می‌دهد: 65536. این عدد نشان‌دهنده تعداد کل مراجعی است که می‌تواند توسط کد در یک فایل بایت کد اجرایی Dalvik (DEX) فراخوانی شود. این صفحه نحوه عبور از این محدودیت را با فعال کردن پیکربندی برنامه معروف به multidex توضیح می‌دهد که به برنامه شما امکان می‌دهد چندین فایل DEX را بسازد و بخواند.

درباره محدودیت مرجع 64K

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

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

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

نسخه‌های پلتفرم قبل از Android 5.0 (سطح API 21) از زمان اجرا 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 برای اندروید 5.0 و بالاتر

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

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

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

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

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

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

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

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

برنامه خود را برای 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"
    }
    

    کاتلین

    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 33
            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() یا هر کد دیگری را از طریق Reflection یا JNI اجرا نکنید. ردیابی Multidex آن تماس ها را دنبال نمی کند و باعث ایجاد ClassNotFoundException یا تأیید خطا به دلیل پارتیشن کلاس بد بین فایل های DEX نمی شود.

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

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

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

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

  • نصب فایل‌های DEX در حین راه‌اندازی روی پارتیشن داده دستگاه پیچیده است و اگر فایل‌های DEX ثانویه بزرگ باشند، می‌تواند منجر به خطاهای Application Not Responding (ANR) شود. برای جلوگیری از این مشکل، کوچک کردن کد را فعال کنید تا اندازه فایل‌های DEX به حداقل برسد و بخش‌های استفاده نشده از کد حذف شود.
  • هنگام اجرا بر روی نسخه‌های قبل از Android 5.0 (سطح API 21)، استفاده از multidex برای دور زدن محدودیت linearalloc کافی نیست ( مسأله 37008143 ). این محدودیت در اندروید 4.0 (سطح API 14) افزایش یافته بود، اما مشکل را به طور کامل حل نکرد.

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

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

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

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

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

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

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

برنامه های multidex را تست کنید

هنگامی که تست های ابزار دقیق را برای برنامه های multidex می نویسید، اگر از ابزار 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);
  ...
}