آشنایی با Multidex در اندروید

نویسنده : سید ایوب کوکبی ۱۳ شهریور ۱۳۹۸
آشنایی با Multidex در اندروید

بعد از کامپایلِ کدهای جاوا فایلی با پسوندِ class. ایجاد می‌شود که حاویِ bytecodeهای لازم برای اجرا در ماشینِ مجازی جاوا (JVM) است. معمولاً این فایل‌ها در بستۀ فشرده‌ای با پسوندِ jar. ذخیره می‌شوند. در اندروید هم به همین شکل است. کدها توسط کامپایلر به فایلی با پسوند dex. تبدیل می‌شود تا در DVM یا ART که روی دیوایسِ اندروید قرار دارد اجرا شود. این فایل به همراهِ سایرِ ریسورس‌ها در فایلِ apk قرار می‌گیرد. در اغلبِ برنامه‌‌ها فقط یک فایلِ dex وجود دارد ولی گاهی به دلایلی که در این مقاله خواهید خواند ناچار به استفاده از چند فایلِ dex یا اصطلاحاً Multidex هستیم.

سیستمِ Build در اندروید

کامپایلر اندروید، تمامِ کدها و ریسورس‌ها را در پکیجی با پسوندِ apk قرار می‌دهد؛ یعنی همان چیزی که کاربرِ روی دیوایسش نصب می‌کند. همین الان می‌توانید پسوندِ یک فایل apk را به zip تغییر دهید و محتویاتِ داخلش را ببینید. تبدیلِ کدها به apk نیازمندِ ابزارها و فرایندهایِ مختلفی است که در شکلِ زیر می‌توانید ببینید:

مراحلِ معمول برای کامپایلِ یک برنامۀ اندرویدی به شرح ذیل است:

  1. سورس کد به یک فایلِ dex تبدیل می‌شود که حاویِ بایت کدهای لازم برای اجرا روی دستگاه‌های اندرویدی است؛
  2. سپس APK Packager ریسورس‌ها را به همراهِ dex در یک فایلِ apk قرار می‌دهد؛
  3. APK Packager فایلِ APK را امضاء (Sign) می‌کند؛
  4. قبل از تولیدِ فایلِ نهایی، کدها بهینه‌سازی شده و مواردِ زائد حذف می‌شود تا حجمِ فایلِ نهایی کاهش یابد.

Multidex چیست؟

در اندروید، هر فایلِ dex محدود به ۶۵۵۳۶ متد است و بیشتر از آن باعثِ خطای کامپایل می‌شود. این تعداد شاملِ متدهای فریم‌ورکِ اندروید، کتابخانه‌های جانبی و البته متدهایی است که خودِ شما نوشته‌اید. اصطلاحاً به این محدودیت ۶۴K reference limit گفته می‌شود. (در کامپیوتر هر کیلو برابر ۱۰۲۴ است و ۶۴ ضربدر ۱۰۲۴ می‌شود ۶۵۵۳۶).

البته این اتفاق به ندرت و تنها در شرایطی که از کتابخانه‌های جانبیِ زیادی استفاده کنید به وجود می‌آید. در حالتِ عادی به سختی می‌توان به این محدودیت رسید. به هر حال اگر به خطاهای زیر برخوردید یعنی به این محدودیت رسیده‌اید:

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

در نسخه‌های قدیمی build system متن خطا صورتِ زیر است:

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

هر دو خطا اشاره به محدودیت ۶۴K reference limit دارند؛ یعنی توسعه‌دهنده بیشتر از ۶۵۵۳۶ متد استفاده کرده است. اینجاست که به قابلیتِ Multidex نیاز پیدا می‌کنیم؛ یعنی استفاده از چند فایلِ dex که در قسمتِ بعدی نحوۀ استفاده از آن را توضیح می‌دهیم.

بخوانید  آموزش زبان کاتلین – درس 21 (Visibility Modifiers)

پشتیبانی از Multidex در اندرویدِ پایین‌تر از ۵٫۰

در نسخه‌های پایین‌تر از اندروید ۵ رانتایمِ قدیمیِ دالویک استفاده می‌شود که برای هر فایلِ APK فقط می‌تواند یک classed.dex داشته باشد. بنابراین برای پشتیبانی از multidex در API Level پایین‌تر از ۲۱ باید multidex suport library را به پروژۀ خود اضافه کنید:

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

نسخه‌های فعلیِ این کتابخانه را می‌توانید در این صفحه ببینید. در صورتی که از androidX استفاده نمی‌کنید می‌توانید به جای بالایی از این dependency استفاده کنید:

dependencies {
  implementation 'com.android.support:multidex:1.0.3'
}

با این کار کتابخانه به عنوان بخشی از اولین فایلِ dex دسترسی به سایرِ dex fileها را مدیریت می‌کند.

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

اندروید ۵٫۰ (API Level 21) و بالاتر، از رانتایمِ جدیدِ ART استفاده می‌کند که به صورت native از فایل‌های APK حاوی بیش از یک فایلِ dex پشتیبانی می‌کند. این رانتایم پیش از نصبِ برنامه با اسکنِ تمامِ فایل‌های classesN.dex (که N شمارۀ فایل‌های dex است) آن‌ها را شناسایی و در یک فایلِ oat ادغام می‌کند تا روی دستگاه اجرا شود. بنابراین اگر در برنامۀ شما مقدار minSdkVersion مقدارش ۲۱ و بالاتر بود multidex به صورت پیش‌فرض رویِ آن فعال است.

با اضافه کردنِ این کتابخانه، برنامه قادر است به بیش از یک فایلِ dex دسترسی پیدا کند. به عبارتِ دیگر اگر بیش از ۶۴k متد در برنامۀ شما وجود داشت به این کتابخانه نیاز دارید.

چگونه به محدودیتِ ۶۴k گرفتار نشویم

پیش از استفاده از multidex ابتدا با پاکسازی و حذفِ کدهای اضافه گرفتارِ این محدودیت نشوید چون استفاده از آن با محدودیت‌ها و دردسرهایی همراه است. کتابخانه‌های اضافه را حذف کنید و برای کتابخانه‌های بزرگ سعی کنید فقط آن قسمتی که نیاز دارید را ایمپورت کنید. یکی از اشتباهاتِ مهمِ برنامه‌نویسانِ اندروید این است که کتابخانۀ بزرگی را واردِ برنامه کرده ولی فقط از یکی دو تا متدش استفاده می‌کنند. این یکی از مهم‌ترین دلایلِ به وجود آمدن محدودیت ۶۴k است. کارِ دیگری که می‌توانید بکنید فعال کردن code shriknking در پروگارد (ProGuard) است. این قابلیت اطمینان می‌دهد که هیچ کدِ بلااستفاده‌ای در فایل APK قرار نخواهد گرفت. استفاده از این تکنیک‌ها ضمنِ کاهشِ حجمِ نهاییِ فایل نیاز به استفاده از multidex را به حداقل می‌رساند.

پیکربندیِ برنامه برای multidex

برای minSdkVersion 21 و بالاتر نیازی به هیچ ساپورت لایبرری نیست ولی برای ۲۰ و پایین‌تر باید multidex support library را اضافه کنید و تغییرات زیر را در پروژه اعمال کنید:

۱- فایل build-gradle (سطح ماژول) را به شکلِ زیر تغییر دهید تا از multidex حمایت کند:

android {
    defaultConfig {
        ...
        minSdkVersion 15 
        targetSdkVersion 28
        multiDexEnabled true
    }
    ...
}

dependencies {
  implementation 'com.android.support:multidex:1.0.3'
}

۲٫ بسته به اینکه کلاسِ Application را Override کرده باشید یا نه یکی از کارهای زیر را انجام دهید:

  • اگر کلاسِ Application را Override نکرده‌اید فایلِ مانیفست را باز کنید و قسمتِ android:name را به صورتِ زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application
            android:name="android.support.multidex.MultiDexApplication" >
        ...
    </application>
</manifest>
  • در صورتِ override کردنِ کلاسِ Application آن را به این صورت تغییر دهید تا کلاسِ MultiDexApplication را اکستند کند؛ البته اگر امکانش بود:
public class MyApplication extends MultiDexApplication { ... }
public class MyApplication extends MultiDexApplication { ... }
  • اگر امکانِ چنین کاری میسر نبود و از قبل کلاسِ دیگری را اکستند کرده بودید به جایش متدِ attachBaseContext را Override کرده و پشتیبانی از Multidex را به آن اضافه کنید:
public class MyApplication extends SomeOtherApplication {
  @Override
  protected void attachBaseContext(Context base) {
     super.attachBaseContext(base);
     MultiDex.install(this);
  }
}

نکته: هیچگاه متد MultiDex.install یا هر کدِ دیگری را از طریقِ رفلکشن یا JNI قبل از پایانِ اجرای MultiDex.install اجرا نکنید؛ چرا که Multidex آن فرخوانی‌ها را ردیابی نمی‌کند و باعثِ بروزِ استثنایِ ClassNotFoundException خواهد شد.

بخوانید  آموزش زبان کاتلین – درس 30 (سربارگذاری عملگرها)

با انجامِ این تغییرات، Android Build Tools یک فایلِ اولیۀ DEX با نامِ classes.dex درست می‌کند و در صورتِ نیاز از فایل‌هایِ دیگر مثل classes2.dex, classes3.dex و … پشتیبانی خواهد کرد. سیستم بیلدینگ در نهایت همۀ فایل‌هایِ dex را درون یک فایلِ APK قرار می‌دهد. در رانتایم، Multidex API هنگامِ رسیدن به یک متد به جای اینکه فقط فقط در فایلِ classes.dex جستجو کند در تمامِ dex file ها آن متد را جستجو خواهد کرد.

محدودیت‌های Multidex Support Library

استفاده از multidex با محدودیت‌های همراه هست که پیش از انتشارِ برنامه حتماً باید به آن توجه کنید:

  • نصبِ فایل‌های DEX در پارتیشنِ data کمی پیچیده است و اگر فایل‌های ثانویۀ dex بزرگ باشند ممکن است برنامه با خطای Application Not Responding مواجه شود. در این حالت لازم است قابلیتِ Code Shrikning در پروگارد را فعال کرده تا با حذفِ قسمت‌های اضافه حجمِ فایل‌های dex را کاهش دهد؛
  • استفاده از multidex در اندرویدِ پایین‌تر ۵٫۰ (API Level 21) خالی از اشکال نیست. و در نسخه‌های پایین‌تر از ۴٫۰ یعنی API Level 14 و پایین‌تر مشکلات بیشتر هم می‌شود. در صورتی که اصرار دارید از این نسخه‌ها کماکان پشتیبانی کنید حتماً برنامه را پیش از انتشار روی گروهی از دیوایس‌ها تست کنید؛ چون ممکن است با فراخوانی گروهِ مشخصی از کلاس‌ها برنامه کرش کند.

code shrinking می‌تواند احتمالِ بروزِ این مشکلات را کاهش دهد.

بهینه‌سازیِ سرعتِ بیلد در برنامه‌های multidex

برنامه‌هایِ multidex به صورت چشمگیری سرعت بیلد را کاهش می‌دهند. دلیلِ این موضوع افزایشِ تصمیمات پیچید در build system است؛ اینکه کدام کلاس را در dex file اولیه قرار دهید و کدام یک را در فایل‌های dex ثانویه فعالیتِ مازادی است که بر دوشِ کامپایلر است. build افزایشی با استفاده از multidex عموماً به قیمتِ طولانی و کند شدن زمان بیلد تمام می‌شود.

بخوانید  نشت حافظه در اندروید! هر آنچه لازم است بدانید

برای کاهشِ این زمان باید از قابلیتِ pre-dexing استفاده کنید تا استفادۀ مجدد از خروجیِ multidex در بیلدهای مکرر میسر شود. این قابلیت به رانتایمِ ART وابسته است که فقط در اندروید ۵٫۰ و بالاتر وجود دارد. در صورتی که از اندروید استودیو ۲٫۳ و بالاتر استفاده می‌کنید، IDE به صورتِ خودکار هنگامِ دیپلوی کردن برنامه روی دیوایس‌هایی با اندرویدِ ۵ و بالاتر از این قابلیت استفاده می‌کند. (ویژگیِ یاد شده در اندروید استودیو ۳٫۰٫۰ و بالاتر بهبودهای زیادی یافته است).

در صورتِ اجرای گریدل از خطِ فرمان لازم است 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 what you set for
            // 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 'com.android.support:multidex:1.0.3'
}

نتیجه‌گیری

۶۴k reference limit به معنیِ گذشتن از مرزِ ۶۵۵۳۶ متدی فایل dex است. اینجا باید قابلیت multidex را در برنامه فعال کنید تا روی دستگاه‌های اندرویدی اجرا شود. این قابلیت به صورت پیش‌فرض در API Level 21 و بالاتر فعال است و برای پایین‌تر از آن باید به صورت دستی فعال کنید که در بالا توضیح دادیم. حتماً فایل‌ها را Shrink کنید تا احیاناً با خطاهایِ عجیبِ dex مواجه نشوید و تا حد امکان طوری کدها را سروسامان دهید که مجبور نشوید از multidex استفاده کنید. این باید آخرین انتخابِ شما باشد.

سید ایوب کوکبی

نویسنده و مترجم...

0 دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *