تفعيل ميزة "الاتصال المتعدد" للتطبيقات التي تتضمّن أكثر من 64 ألف طريقة

إذا كان تطبيقك يحتوي على minSdk من المستوى 20 لواجهة برمجة التطبيقات أو مستوى أقل وكان تطبيقك والملفان البرمجيان المُشار إليهما يتضمّنان أكثر من 65,536 طريقة، سيظهر لك خطأ الإنشاء التالي الذي يشير إلى أنّ تطبيقك وصل إلى الحد الأقصى لبنية إنشاء 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 متعددة وقراءتها.

لمحة عن الحد الأقصى المسموح به للمراجع الذي يبلغ 64 كيلوبايت

تحتوي ملفات تطبيق Android (APK) على ملفات رمز برمجي قابل للتنفيذ في شكل ملفات Dalvik القابلة للتنفيذ (DEX)، والتي تحتوي على الرمز المجمّع المستخدَم لتشغيل تطبيقك. تحدّ مواصفات Dalvik القابلة للتنفيذ من إجمالي عدد الطرق التي يمكن الإشارة إليها في ملف DEX واحد إلى 65,536، بما في ذلك طرق إطار عمل Android وطرق المكتبة والطرق الواردة في الرمز البرمجي الخاص بك.

في سياق علوم الكمبيوتر، يشير مصطلح الكيلو أو K إلى 1024 (أو 2^10). وبما أنّ 65,536 يساوي 64×1024، يُشار إلى هذا الحدّ باسم _الحدّ المرجعي البالغ 64 كيلوبايت_.

إتاحة حِزم Multidex قبل الإصدار 5.0 من Android

تستخدم إصدارات النظام الأساسي الأقدم من Android 5.0 (المستوى 21 لواجهة برمجة التطبيقات) وقت تشغيل Dalvik لتنفيذ رمز التطبيق. يحصر Dalvik التطبيقات تلقائيًا في استخدام ملف bytecode واحد فقط classes.dex لكل حزمة APK. للتغلب على هذا القيد، أضِف مكتبة multidex إلى ملف build.gradle أو build.gradle.kts على مستوى الوحدة:

رائع

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

Kotlin

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

وتصبح هذه المكتبة جزءًا من ملف DEX الأساسي لتطبيقك، ثم تدير الوصول إلى ملفات DEX الإضافية والرمز البرمجي الذي تحتوي عليه. للاطّلاع على الإصدارات الحالية لهذه المكتبة، يُرجى الاطّلاع على إصدارات حِزم APK المتعددة.

لمزيد من التفاصيل، اطّلِع على القسم المعنيّ بكيفية ضبط تطبيقك للاستفادة من ميزة "متعدد الإصدارات".

إتاحة حِزم Multidex لنظام التشغيل Android 5.0 والإصدارات الأحدث

يستخدم نظام التشغيل Android 5.0 (المستوى 21 لواجهة برمجة التطبيقات) والإصدارات الأحدث بيئة تشغيل تُعرف باسم ART تسمح بشكلٍ أساسي بتحميل ملفات DEX متعددة من ملفات APK. تُجري أداة ART compiling pre-compilation (الترجمة المسبقة) في وقت تثبيت التطبيق، وذلك من خلال البحث عن ملفات classesN.dex compiling وتحويلها إلى ملف OAT compiling واحد لكي ينفِّذه جهاز Android. لذلك، إذا كان الإصدار minSdkVersion هو 21 أو إصدار أحدث، يتم تفعيل أداة MultiDex تلقائيًا ولن تحتاج إلى مكتبة MultiDex.

لمزيد من المعلومات عن وقت تشغيل Android 5.0 ، يُرجى الاطّلاع على مقالة Android Runtime (ART) وDalvik.

ملاحظة: عند تشغيل تطبيقك باستخدام Android Studio، يتم تحسين الإصدار للأجهزة المستهدفة التي يتم نشره عليها. ويشمل ذلك تفعيل ميزة "متعدد الحِزم" عندما تعمل الأجهزة المستهدَفة بالإصدار 5.0 من نظام التشغيل Android والإصدارات الأحدث. بما أنّه لا يتم تطبيق هذا التحسين إلا عند نشر تطبيقك باستخدام IDE IDE Android Studio، قد يظل عليك ضبط إصدار الإصدار لميزة "حِزم APK متعددة" لتجنُّب الحد الأقصى البالغ 64 كيلوبايت.

تجنُّب الحدّ الأقصى البالغ 64 كيلوبايت

قبل ضبط إعدادات تطبيقك لتفعيل استخدام 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"
    }
    

    Kotlin

    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 class، غيِّرها لتوسيع MultiDexApplication، على النحو التالي:

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • إذا تم إلغاء Application الفئة ولكن تعذّر تغيير الفئة الأساسية، بدلاً من ذلك ألغِ طريقة attachBaseContext() واستخدِم MultiDex.install(this) لتفعيل ملف multidex:

      Kotlin

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

      Java

      public class MyApplication extends SomeOtherApplication {
        @Override
        protected void attachBaseContext(Context base) {
           super.attachBaseContext(base);
           MultiDex.install(this);
        }
      }
      

      تحذير: لا تنفِّذ MultiDex.install() أو أي رمز برمجي آخر من خلال ميزة Reflection أو JNI قبل اكتمال MultiDex.install(). لن يتّبع تتبُّع Multidex هذه الطلبات، ما يؤدي إلى حدوث أخطاء ClassNotFoundException أو أخطاء في إثبات الملكية بسبب تقسيم فئة غير صحيح بين ملفات DEX.

الآن عند إنشاء تطبيقك، تُنشئ أدوات إنشاء Android ملف DEX أساسيًا (classes.dex) وملفات DEX داعمة (classes2.dex وclasses3.dex وما إلى ذلك) حسب الحاجة. بعد ذلك، يُجمِّع نظام الإنشاء جميع ملفات DEX في حزمة APK.

أثناء التشغيل، بدلاً من البحث في ملف classes.dex الرئيسي فقط، تستخدِم واجهات برمجة تطبيقات حِزم DEX المتعددة أداة تحميل فئة خاصة للبحث في جميع ملفّات classes.dex المتاحة عن طريقطك.

قيود مكتبة multidex

تفرض مكتبة multidex بعض القيود المعروفة. عند دمج المكتبة في إعدادات إنشاء تطبيقك، يجب مراعاة ما يلي:

  • إنّ عملية تثبيت ملفات DEX أثناء بدء التشغيل على قسم بيانات الجهاز معقّدة، ويمكن أن تؤدي إلى أخطاء "التطبيق لا يستجيب" (ANR) إذا كانت ملفات DEX الثانوية كبيرة. لتجنُّب هذه المشكلة، يمكنك تفعيل ميزة تقليل حجم الرموز البرمجية لتقليل حجم ملفات DEX وإزالة الأجزاء غير المستخدَمة من الرموز البرمجية.
  • عند التشغيل على إصدارات أقدم من Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات)، لا يكفي استخدام حزمة multidex للتعامل مع الحد الأقصى لمساحة التخزين في ذاكرة التخزين المؤقت (الخطأ 37008143). تم رفع هذا الحدّ في الإصدار 4.0 من نظام التشغيل Android (المستوى 14 من واجهة برمجة التطبيقات)، ولكنّ ذلك لم يحلّ المشكلة بالكامل.

    في الإصدارات الأقدم من Android 4.0، قد تصل إلى الحد الأقصى لمساحة التخزين في ذاكرة التخزين المؤقت قبل بلوغ الحد الأقصى لفهرسة DEX. لذلك، إذا كنت تستهدِف مستويات أقل من واجهة برمجة التطبيقات 14، عليك إجراء اختبار شامل على هذه الإصدارات من المنصة، لأنّ تطبيقك قد يواجه مشاكل عند بدء التشغيل أو عند تحميل مجموعات معيّنة من الفئات.

    يمكن أن تقلِّل تصغير الرموز البرمجية من هذه المشاكل أو تقضي عليها.

تحديد الفئات المطلوبة في ملف DEX الأساسي

عند إنشاء كل ملف DEX لتطبيق متعدّد DEX، تُجري أدوات الإنشاء عملية اتخاذ قرارات معقدة لتحديد الفئات المطلوبة في ملف DEX الأساسي كي يتمكّن تطبيقك من البدء بنجاح. إذا لم يتم توفير أي فئة مطلوبة أثناء بدء التشغيل في ملف DEX الأساسي، سيتعطّل تطبيقك مع ظهور الخطأ java.lang.NoClassDefFoundError.

تتعرّف أدوات الإنشاء على مسارات الرموز البرمجية للرمز الذي يتم الوصول إليه مباشرةً من رمز تطبيقك. ومع ذلك، يمكن أن تحدث هذه المشكلة عندما تكون مسارات الرموز البرمجية أقل ظهورًا، مثل عندما تحتوي المكتبة التي تستخدمها على تبعيات معقّدة. على سبيل المثال، إذا كان الرمز يستخدم ميزة "الاستكشاف الذاتي" أو استدعاء methods من 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')
            ...
        }
    }
}

Kotlin

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

تحسين حِزم multidex في إصدارات التطوير

تتطلّب عملية إعداد حِزم متعددة بتنسيق DEX وقتًا متزايدًا بشكل كبير لمعالجة عملية الإنشاء، لأنّ نظام الإنشاء يجب أن يتّخذ قرارات معقّدة بشأن الفئات التي يجب تضمينها في ملف DEX الأساسي والفئات التي يمكن تضمينها في ملفات DEX الثانوية. وهذا يعني أنّ عمليات الإنشاء المتزايدة باستخدام multidex تستغرق عادةً وقتًا أطول ويمكن أن تبطئ عملية التطوير.

لتقليل أوقات الإنشاء المتزايدة، استخدِم المعالجة المسبقة لملف dex لإعادة استخدام ناتج حِزم multidex بين عمليات الإنشاء. تعتمد ميزة "المعالجة المسبقة للغة البرمجة (dexing)" على تنسيق ART غير المتوفّر إلا على الإصدار 5.0 من Android (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث. إذا كنت تستخدم Android Studio، ستستخدم بيئة التطوير المتكاملة تلقائيًا ميزة "الترميز المُسبق" عند نشر تطبيقك على جهاز يعمل بالإصدار 5.0 من نظام التشغيل Android (المستوى 21 من واجهة برمجة التطبيقات) أو إصدار أحدث. ومع ذلك، إذا كنت تُشغّل عمليات إنشاء Gradle من سطر الأوامر، عليك ضبط minSdkVersion على 21 أو إصدار أحدث لتفعيل ميزة "المعالجة المسبقة للغة برمجة Java".

للحفاظ على الإعدادات في الإصدار العلني، يمكنك إنشاء نسختَين من تطبيقك باستخدام نكهات المنتجات، نسخة واحدة بنكهة التطوير ونسخة واحدة بنكهة الإصدار، مع قيم مختلفة للمتغير 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"
}

Kotlin

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" أو سطر الأوامر، يمكنك الاطّلاع على مقالة تحسين سرعة الإنشاء. لمزيد من المعلومات حول استخدام الأسعار المتغيرة للإصدار، يُرجى الاطّلاع على مقالة ضبط الأسعار المتغيرة للإصدار.

ملاحظة: إذا كانت لديك أنواع مختلفة من عمليات الإنشاء لمختلف احتياجات حِزم APK المتعددة، يمكنك تقديم ملف بيان مختلف لكل نوع حتى لا يغيّر الملف المخصّص لمستوى واجهة برمجة التطبيقات 20 والإصدارات الأقدم سوى اسم علامة <application>. يمكنك أيضًا إنشاء فئة فرعية مختلفة من Application لكل صيغة لكي تتمكّن فقط الفئة الفرعية للمستوى 20 من واجهة برمجة التطبيقات والإصدارات الأقدم من توسيع فئة MultiDexApplication أو استدعاء MultiDex.install(this).

اختبار التطبيقات التي تستخدم ملفات DEX متعددة

عند كتابة اختبارات أداة القياس لتطبيقات حِزم متعددة، لن تكون هناك حاجة إلى ضبط أي إعدادات إضافية إذا كنت تستخدم أداة قياس MonitoringInstrumentation أو AndroidJUnitRunner. إذا كنت تستخدم Instrumentation آخر، عليك إلغاء طريقة onCreate() باستخدام الرمز التالي:

Kotlin

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

Java

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