إنشاء حِزم 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 لـ Groovy، أو isEnable لنصوص لغة Kotlin
في حال ضبط هذا العنصر على true، ينشئ Gradle عدة ملفات APK استنادًا إلى واجهات ABI التي تحدّدها. القيمة التلقائية هي false.
exclude
تحدِّد هذه السياسة قائمة مفصولة بفواصل من واجهات ABI التي لا تريد أن تنشئ لها Gradle حِزم APK منفصلة. يمكنك استخدام exclude إذا كنت تريد إنشاء حِزم APK لمعظم واجهات ABI ولكنك تريد استبعاد بعض واجهات 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 لـ Groovy، أو 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++ ، تتضمّن لوحة إنشاء الصيغ ثلاثة أعمدة: وحدة، وصيغة نشطة للإصدار، وواجهة ABI النشطة، كما هو موضّح في الشكل 2.

الشكل 2. تضيف لوحة إنشاء صيغ عمود Active 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 ABI على versionCode من عام 2004 وحصلت 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 عامة.

رائع

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 (للتطبيق Groovy) أو في المجموعة splits.abi في ملف build.gradle.kts الخاص بك (لنص برمجي بلغة Kotlin).isUniversalApk = true

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

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

modulename-screendensityABI-buildvariant.apk

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

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

في حال تفعيل عدة حِزم APK لواجهة ABI، يتم تحديد واجهة التطبيق الثنائية (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 وواجهة ABI لـ x86 تحتوي على اسم ملف APK هو myApp-mdpiX86-release.apk.