إنشاء حِزم APK متعددة

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

يمكن لـ Gradle إنشاء حِزم APK منفصلة تحتوي فقط على الرموز البرمجية والموارد الخاصة بكل واجهة ثنائية للتطبيق (ABI). توضّح هذه الصفحة كيفية ضبط عملية الإنشاء لإنتاج حِزم APK متعددة. إذا كنت بحاجة إلى إنشاء إصدارات مختلفة من تطبيقك لا تستند إلى واجهة ABI، يمكنك استخدام تنويعات التصميم بدلاً من ذلك.

ضبط الإصدار لحِزم APK متعددة

لضبط عملية الإنشاء لحِزم APK متعددة، أضِف حزمة splits إلى ملف build.gradle على مستوى الوحدة. ضمن مجموعة splits، قدِّم مجموعة abi تحدّد الطريقة التي تريد أن ينشئ بها Gradle حِزم APK لكل واجهة ABI.

ضبط حِزم APK متعددة لواجهات التطبيقات الثنائية

لإنشاء حِزم APK منفصلة لواجهات ABI مختلفة، أضِف مجموعة abi داخل مجموعة splits. في مجموعة abi، قدِّم قائمة بواجهات ABI التي اخترتها.

تُستخدَم خيارات Gradle DSL التالية لضبط حِزم APK متعددة لكل واجهة ABI:

‫enable لـ Groovy أو isEnable لنص Kotlin البرمجي
إذا ضبطت هذا العنصر على true، سينشئ Gradle حِزم APK متعددة استنادًا إلى واجهات ABI التي تحدّدها. تكون القيمة التلقائية false.
exclude
تحدِّد قائمة قيم مفصولة بفاصلة لواجهات التطبيق الثنائية (ABI) التي لا تريد أن ينشئ Gradle حِزم APK منفصلة لها. استخدِم exclude إذا أردت إنشاء حِزم APK لمعظم واجهات التطبيقات الثنائية (ABI) ولكنك تحتاج إلى استبعاد بعض واجهات التطبيقات الثنائية (ABI) التي لا يتيحها تطبيقك.
reset()

لمحو القائمة التلقائية لواجهات ABI لا تستخدِم هذه السمة إلا مع العنصر include لتحديد واجهات التطبيق الثنائية التي تريد إضافتها.

يضبط المقتطف التالي قائمة واجهات التطبيق الثنائية (ABI) على x86 وx86_64 فقط من خلال استدعاء reset() لمحو القائمة، ثم استخدام include:

reset()                 // Clears the default list from all ABIs to no ABIs.
include "x86", "x86_64" // Specifies the two ABIs we want to generate APKs for.
include

تحدّد قائمة قيم مفصولة بفاصلة لواجهات التطبيقات الثنائية (ABI) التي تريد أن ينشئ Gradle حِزم APK لها. لا تستخدِم هذه السمة إلا مع reset() لتحديد قائمة دقيقة بواجهات التطبيق الثنائية (ABI).

‫universalApk لـ Groovy أو isUniversalApk لنص Kotlin البرمجي

إذا كانت القيمة true، ينشئ Gradle حزمة APK عامة بالإضافة إلى حِزم APK لكل واجهة ABI. تحتوي حزمة APK العامة على الرموز البرمجية والموارد لجميع الواجهات الثنائية للتطبيق (ABI) في حزمة APK واحدة. تكون القيمة التلقائية false.

ينشئ المثال التالي حزمة APK منفصلة لكل واجهة ثنائية للتطبيق (ABI): x86 وx86_64. يتم ذلك باستخدام reset() للبدء بقائمة فارغة من واجهات ABI، متبوعة بـ include مع قائمة بواجهات ABI التي تحصل كل منها على حزمة APK.

أنيق

android {
  ...
  splits {

    // Configures multiple APKs based on ABI.
    abi {

      // Enables building multiple APKs per ABI.
      enable true

      // By default all ABIs are included, so use reset() and include to specify that you only
      // want APKs for x86 and x86_64.

      // Resets the list of ABIs for Gradle to create APKs for to none.
      reset()

      // Specifies a list of ABIs for Gradle to create APKs for.
      include "x86", "x86_64"

      // Specifies that you don't want to also generate a universal APK that includes all ABIs.
      universalApk false
    }
  }
}

Kotlin

android {
  ...
  splits {

    // Configures multiple APKs based on ABI.
    abi {

      // Enables building multiple APKs per ABI.
      isEnable = true

      // By default all ABIs are included, so use reset() and include to specify that you only
      // want APKs for x86 and x86_64.

      // Resets the list of ABIs for Gradle to create APKs for to none.
      reset()

      // Specifies a list of ABIs for Gradle to create APKs for.
      include("x86", "x86_64")

      // Specifies that you don't want to also generate a universal APK that includes all ABIs.
      isUniversalApk = false
    }
  }
}

للاطّلاع على قائمة بواجهات التطبيق الثنائية (ABI) المتوافقة، يُرجى مراجعة واجهات التطبيق الثنائية (ABI) المتوافقة.

المشاريع التي لا تتضمّن رمزًا برمجيًا أصليًا أو رمز C++‎

بالنسبة إلى المشاريع التي لا تتضمّن رمزًا برمجيًا أصليًا أو رمزًا برمجيًا بلغة C++‎، تحتوي لوحة تنويعات التصميم على عمودَين: الوحدة وتنويعة التصميم النشطة، كما هو موضّح في الشكل 1.

تحتوي لوحة Build Variants (تنويعات التصميم) على عمودَين للمشاريع التي لا تتضمّن رمزًا برمجيًا أصليًا أو رمزًا برمجيًا بلغة C++‎.
الشكل 1. تحتوي لوحة تنويعات التصميم على عمودَين للمشاريع التي لا تتضمّن رمزًا برمجيًا أصليًا أو رمز C++‎.

تحدّد قيمة تنويعة التصميم النشطة للوحدة تنويعة التصميم التي يتم نشرها وتظهر في المحرِّر. للتبديل بين التنويعات، انقر على خلية تنويعة التصميم النشطة لوحدة معيّنة واختَر التنويعة التي تريدها من حقل القائمة.

المشاريع التي تتضمّن رموزًا برمجية أصلية أو بلغة C++‎

بالنسبة إلى المشاريع التي تتضمّن رمزًا برمجيًا أصليًا أو رمزًا برمجيًا بلغة C++‎، تحتوي لوحة تنويعات التصميم على ثلاثة أعمدة: الوحدة وتنويعة التصميم النشطة وواجهة ABI النشطة، كما هو موضّح في الشكل 2.

تضيف لوحة "تنويعات التصميم" عمود "واجهة ABI النشطة" للمشاريع التي تتضمّن رمزًا برمجيًا أصليًا أو رمز C++‎.
الشكل 2. تضيف لوحة تنويعات التصميم عمود واجهة التطبيق الثنائية (ABI) النشطة للمشاريع التي تتضمّن رمزًا أصليًا أو رمز C++‎.

تحدّد قيمة تنويعة التصميم النشطة للوحدة تنويعة التصميم التي يتم نشرها وتظهر في المحرِّر. بالنسبة إلى الوحدات الأصلية، تحدّد قيمة واجهة التطبيق الثنائية (ABI) النشطة واجهة التطبيق الثنائية التي يستخدمها المحرِّر، ولكنّها لا تؤثر في ما يتم نشره.

لتغيير نوع الإصدار أو واجهة ABI:

  1. انقر على الخلية في عمود تنويعة التصميم النشطة أو واجهة ABI النشطة.
  2. اختَر الصيغة أو واجهة ABI التي تريدها من حقل القائمة. يتم تشغيل مزامنة جديدة تلقائيًا.

يؤدي تغيير أي من العمودين لتطبيق أو وحدة مكتبة إلى تطبيق التغيير على جميع الصفوف التابعة.

ضبط ميزة "الاحتفاظ بنُسخ سابقة"

بشكل تلقائي، عندما ينشئ Gradle عدة حِزم APK، تتضمّن كل حزمة APK معلومات الإصدار نفسها، كما هو محدّد في ملف build.gradle أو build.gradle.kts على مستوى الوحدة. بما أنّ "متجر Google Play" لا يسمح بتعدّد حِزم APK للتطبيق نفسه إذا كانت جميعها تحمل معلومات الإصدار نفسها، عليك التأكّد من أنّ كل حزمة APK لها versionCode فريد قبل تحميلها إلى "متجر Google Play".

يمكنك ضبط ملف build.gradle على مستوى الوحدة لإلغاء versionCode لكل حزمة APK. من خلال إنشاء عملية ربط تحدّد قيمة رقمية فريدة لكل واجهة ثنائية للتطبيق (ABI) تضبط لها حِزم APK متعددة، يمكنك إلغاء رمز الإصدار الناتج بقيمة تجمع بين رمز الإصدار المحدّد ضمن مجموعة defaultConfig أو productFlavors والقيمة الرقمية المحدّدة للواجهة الثنائية للتطبيق (ABI).

في المثال التالي، يحصل ملف APK لواجهة التطبيق الثنائية (ABI) x86 على versionCode بقيمة 2004، وتحصل واجهة التطبيق الثنائية (ABI) x86_64 على versionCode بقيمة 3004.

يتيح لك تعيين رموز الإصدارات بزيادات كبيرة، مثل 1000، تعيين رموز إصدارات فريدة لاحقًا إذا كنت بحاجة إلى تحديث تطبيقك. على سبيل المثال، إذا تكرر defaultConfig.versionCode إلى 5 في تحديث لاحق، يعيّن Gradle versionCode من 2005 إلى حزمة APK x86 و3005 إلى حزمة APK x86_64.

أنيق

android {
  ...
  defaultConfig {
    ...
    versionCode 4
  }
  splits {
    ...
  }
}

// Map for the version code that gives each ABI a value.
ext.abiCodes = ['armeabi-v7a':1, x86:2, x86_64:3]

import com.android.build.OutputFile

// For each APK output variant, override versionCode with a combination of
// ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
android.applicationVariants.all { variant ->

  // Assigns a different version code for each output APK
  // other than the universal APK.
  variant.outputs.each { output ->

    // Stores the value of ext.abiCodes that is associated with the ABI for this variant.
    def baseAbiVersionCode =
            // Determines the ABI for this variant and returns the mapped value.
            project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))

    // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
    // the following code doesn't override the version code for universal APKs.
    // However, because you want universal APKs to have the lowest version code,
    // this outcome is desirable.
    if (baseAbiVersionCode != null) {

      // Assigns the new version code to versionCodeOverride, which changes the
      // version code for only the output APK, not for the variant itself. Skipping
      // this step causes Gradle to use the value of variant.versionCode for the APK.
      output.versionCodeOverride =
              baseAbiVersionCode * 1000 + variant.versionCode
    }
  }
}

Kotlin

android {
  ...
  defaultConfig {
    ...
    versionCode = 4
  }
  splits {
    ...
  }
}

// Map for the version code that gives each ABI a value.
val abiCodes = mapOf("armeabi-v7a" to 1, "x86" to 2, "x86_64" to 3)

import com.android.build.api.variant.FilterConfiguration.FilterType.*

// For each APK output variant, override versionCode with a combination of
// abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
androidComponents {
    onVariants { variant ->

        // Assigns a different version code for each output APK
        // other than the universal APK.
        variant.outputs.forEach { output ->
            val name = output.filters.find { it.filterType == ABI }?.identifier

            // Stores the value of abiCodes that is associated with the ABI for this variant.
            val baseAbiCode = abiCodes[name]
            // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
            // the following code doesn't override the version code for universal APKs.
            // However, because you want universal APKs to have the lowest version code,
            // this outcome is desirable.
            if (baseAbiCode != null) {
                // Assigns the new version code to output.versionCode, which changes the version code
                // for only the output APK, not for the variant itself.
                output.versionCode.set(baseAbiCode * 1000 + (output.versionCode.get() ?: 0))
            }
        }
    }
}

لمزيد من الأمثلة على مخططات رموز الإصدارات البديلة، يُرجى الاطّلاع على تحديد رموز الإصدارات.

إنشاء حِزم APK متعددة

بعد ضبط ملف build.gradle أو build.gradle.kts على مستوى الوحدة لإنشاء حِزم APK متعددة، انقر على إنشاء > إنشاء حزمة APK لإنشاء جميع حِزم APK للوحدة المحدّدة حاليًا في لوحة المشروع. ينشئ Gradle حِزم APK لكل واجهة ABI في دليل build/outputs/apk/ الخاص بالمشروع.

ينشئ Gradle حزمة APK لكل واجهة ABI تضبط لها حِزم APK متعددة.

على سبيل المثال، يتيح مقتطف build.gradle التالي إنشاء حِزم APK متعددة لواجهات التطبيق الثنائية (ABI) x86 وx86_64:

أنيق

...
  splits {
    abi {
      enable true
      reset()
      include "x86", "x86_64"
    }
  }

Kotlin

...
  splits {
    abi {
      isEnable = true
      reset()
      include("x86", "x86_64")
    }
  }

يتضمّن الناتج من نموذج الإعداد حِزم APK الأربع التالية:

  • ‫app-X86-release.apk: يحتوي على الرموز والموارد الخاصة بواجهة التطبيق الثنائية (ABI) لـ x86.
  • ‫app-X86_64-release.apk: يحتوي على الرموز والموارد الخاصة بواجهة التطبيق الثنائية (ABI) لـ x86_64.

عند إنشاء حِزم APK متعددة استنادًا إلى واجهة ABI، لا ينشئ Gradle سوى حزمة APK تتضمّن الرمز البرمجي والموارد لجميع واجهات ABI إذا حدّدت universalApk true في قسم splits.abi في ملف build.gradle (بالنسبة إلى Groovy) أو isUniversalApk = true في قسم splits.abi في ملف build.gradle.kts (بالنسبة إلى نص Kotlin البرمجي).

تنسيق اسم ملف APK

عند إنشاء حِزم APK متعددة، ينشئ Gradle أسماء ملفات حِزم APK باستخدام المخطط التالي:

modulename-ABI-buildvariant.apk

مكوّنات المخطط هي:

modulename
تحدّد هذه السمة اسم الوحدة التي يتم إنشاؤها.
ABI
في حال تفعيل حِزم APK متعددة لواجهة ABI، يتم تحديد واجهة ABI لحزمة APK، مثل x86.
buildvariant
تحدّد هذه السمة تنويعة التصميم التي يتم إنشاؤها، مثل debug.