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

تنبيه: منذ أغسطس 2021، يجب نشر جميع التطبيقات الجديدة على شكل حِزم تطبيقات. إذا نشرت تطبيقك على Google Play، أنشئ مجموعة حزمات تطبيق Android وحمِّلها. عند إجراء ذلك، ينشئ Google Play تلقائيًا حِزم APK محسَّنة ويعرضها بما يتناسب مع إعدادات جهاز كل مستخدم، وبالتالي لا يتم تنزيل سوى الرموز والموارد اللازمة لتشغيل تطبيقك. ويكون نشر حِزم APK متعددة مفيدًا إذا كنت تنشر تطبيقًا على متجر لا يتيح استخدام تنسيق AAB. في هذه الحالة، عليك إنشاء كل حِزمة APK وتوقيعها وإدارتها بنفسك.

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

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

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

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

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

لإنشاء حِزم 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) ولكن عليك استبعاد بعض واجهات التطبيق الثنائية التي لا يتوافق معها تطبيقك.
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.

Groovy

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
    }
  }
}

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

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

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

لوحة "إنشاء صيغ البناء"
الشكل 1. تحتوي لوحة خيارات الإصدار على عمودَين للمشاريع التي لا تتضمّن رمزًا برمجيًا بلغة C++ أو رمزًا برمجيًا أصليًا.

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

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

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

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

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

لتغيير نوع الإصدار أو واجهة التطبيق الثنائية (ABI)، اتّبِع الخطوات التالية:

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

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

ضبط نظام إدارة الإصدارات

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

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

في المثال التالي، يحصل حِزم APK الخاصة بواجهة x86 ABI على versionCode بقيمة 2004، بينما تحصل حِزم APK الخاصة بواجهة x86_64 ABI على versionCode بقيمة 3004.

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

ملاحظة: إذا كان الإصدار يتضمّن حزمة APK عامة، عليك تعيين الرمز versionCode لها بحيث يكون أقل من الرمز الخاص بأي من حِزم APK الأخرى. لأنّ "متجر Google Play" يثبّت إصدار تطبيقك المتوافق مع الجهاز المستهدف والذي يتضمّن أعلى قيمة versionCode، فإنّ تحديد قيمة versionCode أقل لحِزمة APK العامة يضمن أنّ "متجر Google Play" سيحاول تثبيت إحدى حِزم APK قبل الرجوع إلى حِزمة APK العامة. يتعامل نموذج الرمز التالي مع هذه الحالة من خلال عدم إلغاء القيمة التلقائية versionCode لحزمة APK عامة.

Groovy

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 متعددة لواجهات التطبيق الثنائية x86 وx86_64:

Groovy

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

Kotlin

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

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

  • app-X86-release.apk: يحتوي على الرموز والموارد الخاصة بواجهة x86 الثنائية للتطبيق.
  • app-X86_64-release.apk: يحتوي على الرموز والموارد الخاصة بواجهة 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.