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

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

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

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

إعداد تصميمك لحِزم APK متعدّدة

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

ضبط حِزم APK متعددة وفقًا لكثافة الشاشة

لإنشاء حِزم APK منفصلة لكثافات شاشات مختلفة، أضِف مجموعة density داخل مجموعة splits. في مجموعة density، قدِّم قائمة بكثافة الشاشة المطلوبة وأحجامها المتوافقة. لا تستخدم قائمة أحجام الشاشات المتوافقة إلا إذا كنت بحاجة إلى عناصر <compatible-screens> محددة في ملف بيان كل حزمة APK.

تُستخدَم خيارات Gradle DSL التالية لضبط حِزم APK متعدّدة لكثافات الشاشة:

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

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

يضبط المقتطف التالي قائمة الكثافات على ldpi وxxhdpi فقط من خلال طلب reset() لمحو القائمة، ثم استخدام include:

reset()                  // Clears the default list from all densities
                         // to no densities.
include "ldpi", "xxhdpi" // Specifies the two densities to generate APKs
                         // for.
include
تحدِّد هذه السياسة قائمة مفصولة بفواصل بالكثافات التي تريد من Gradle إنشاء حِزم APK لها. استخدِم هذه السمة مع reset() فقط لتحديد قائمة دقيقة للكثافات.
compatibleScreens

تحدِّد هذه السياسة قائمة بأحجام الشاشات المتوافقة مفصولة بفواصل. يؤدي ذلك إلى إدخال عقدة <compatible-screens> مطابقة في ملف البيان لكل حزمة APK.

يوفّر هذا الإعداد طريقة سهلة لإدارة كثافات الشاشة وأحجامها في القسم build.gradle نفسه. ومع ذلك، قد يؤدّي استخدام <compatible-screens> إلى فرض قيود على أنواع الأجهزة التي يتوافق معها تطبيقك. للتعرّف على طرق بديلة لإتاحة أحجام الشاشات المختلفة، يمكنك الاطّلاع على نظرة عامة على توافق الشاشة.

بما أنّ كل حزمة APK مستندة إلى كثافة الشاشة تشتمل على علامة <compatible-screens> بقيود محدّدة بشأن أنواع الشاشات المتوافقة مع حزمة APK، حتى في حال نشر عدة حِزم APK، لا تتطابق بعض الأجهزة الجديدة مع فلاتر حِزم APK المتعددة التي تستخدمها. بناءً على ذلك، تنشئ Gradle دائمًا حزمة APK عامة إضافية تحتوي على مواد عرض لجميع كثافات الشاشة ولا تتضمّن علامة <compatible-screens>. يمكنك نشر حزمة APK العامة هذه مع حِزم APK لكل كثافة لتوفير ملف احتياطي للأجهزة التي لا تُطابق حِزم APK التي تحمل علامة <compatible-screens>.

ينشئ المثال التالي ملف APK منفصلاً لكل كثافة شاشة باستثناء ldpi وxxhdpi وxxxhdpi. ويتم إجراء ذلك باستخدام exclude لإزالة تلك الكثافات الثلاث من القائمة التلقائية لجميع الكثافات.

رائع

android {
  ...
  splits {

    // Configures multiple APKs based on screen density.
    density {

      // Configures multiple APKs based on screen density.
      enable true

      // Specifies a list of screen densities you don't want Gradle to create multiple APKs for.
      exclude "ldpi", "xxhdpi", "xxxhdpi"

      // Specifies a list of compatible screen size settings for the manifest.
      compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }
  }
}

Kotlin

android {
    ...
    splits {

        // Configures multiple APKs based on screen density.
        density {

            // Configures multiple APKs based on screen density.
            isEnable = true

            // Specifies a list of screen densities you don't want Gradle to create multiple APKs for.
            exclude("ldpi", "xxhdpi", "xxxhdpi")

            // Specifies a list of compatible screen size settings for the manifest.
            compatibleScreens("small", "normal", "large", "xlarge")
        }
    }
}

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

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

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

تُستخدم خيارات Gradle DSL التالية لإعداد حِزم APK متعددة حسب واجهة التطبيق الثنائية (ABI):

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

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

يضبط المقتطف التالي قائمة واجهات التطبيق الثنائية (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 للنص الرائع أو isUniversalApk لنص بلغة Kotlin

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

يُرجى العلم أنّ هذا الخيار لا يتوفّر إلا في مجموعة splits.abi. عند إنشاء حِزم APK متعددة استنادًا إلى كثافة الشاشة، تنشئ Gradle دائمًا حزمة APK عامة تحتوي على رموز برمجية وموارد تناسب جميع كثافات الشاشات.

ينشئ المثال التالي ملف 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.

لوحة خيارات الإصدار
الشكل 1. تحتوي لوحة إنشاء المتغيرات على عمودين للمشاريع التي لا تحتوي على رموز أصلية/C++.

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

المشاريع باستخدام الرموز الأصلية/C++

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

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

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

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

  1. انقر على الخلية في عمود صيغة الإصدار النشط أو عمود ABI النشط.
  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 على versionCode لعام 2004، بينما تحصل واجهة التطبيق الثنائية (ABI) x86_64 على 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 عامة.

رائع

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]

// For per-density APKs, create a similar map:
// ext.densityCodes = ['mdpi': 1, 'hdpi': 2, 'xhdpi': 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)

// For per-density APKs, create a similar map:
// val densityCodes = mapOf("mdpi" to 1, "hdpi" to 2, "xhdpi" 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 متعددة لها. في حال تفعيل عدة حِزم APK لكل من قيم الكثافة وواجهات ABI، تنشئ Gradle حزمة APK لكل تركيبة كثافة وABI.

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

رائع

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

Kotlin

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

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

  • app-hdpiX86-release.apk: يحتوي على رموز برمجية وموارد لكثافة hdpi وx86 واجهة التطبيق الثنائية (ABI).
  • app-hdpiX86_64-release.apk: يحتوي على رموز برمجية وموارد لكثافة hdpi وx86_64 واجهة التطبيق الثنائية (ABI).
  • app-mdpiX86-release.apk: يحتوي على رموز برمجية وموارد لكثافة mdpi وx86 واجهة التطبيق الثنائية (ABI).
  • app-mdpiX86_64-release.apk: يحتوي على رموز برمجية وموارد لكثافة mdpi وx86_64 واجهة التطبيق الثنائية (ABI).

عند إنشاء حِزم APK متعددة استنادًا إلى كثافة الشاشة، تنشئ Gradle دائمًا حزمة APK عامة تشتمل على رموز وموارد لجميع الكثافات، بالإضافة إلى حِزم APK لكل كثافة.

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

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

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

modulename-screendensityABI-buildvariant.apk

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

modulename
تحدِّد هذه السمة اسم الوحدة التي يتم إنشاؤها.
screendensity
في حال تفعيل عدة حِزم APK لكثافة الشاشة، يتم تحديد كثافة الشاشة لحزمة APK، مثل mdpi.
ABI

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

في حال تفعيل عدة حِزم APK لكل من كثافة الشاشة وABI، يجمع Gradle اسم الكثافة مع اسم ABI، على سبيل المثال mdpiX86. في حال تفعيل universalApk لملفات APK المتوافقة مع واجهة التطبيق الثنائية (ABI)، سيستخدم Gradle universal كجزء من ABI من اسم ملف APK العام.

buildvariant
تحدِّد هذه السمة صيغة الإصدار التي يتم إنشاؤها، مثل debug.

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