چندین APK بسازید

احتیاط: از آگوست ۲۰۲۱، همه برنامه‌های جدید باید به صورت App Bundles منتشر شوند. اگر برنامه خود را در Google Play منتشر می‌کنید، یک Android App Bundle بسازید و آپلود کنید. وقتی این کار را انجام می‌دهید، Google Play به طور خودکار APKهای بهینه شده را برای پیکربندی دستگاه هر کاربر تولید و ارائه می‌دهد، بنابراین آنها فقط کد و منابعی را که برای اجرای برنامه شما نیاز دارند دانلود می‌کنند. انتشار چندین APK در صورتی مفید است که در فروشگاهی منتشر می‌کنید که از فرمت AAB پشتیبانی نمی‌کند. در این صورت، باید خودتان هر APK را بسازید، امضا کنید و مدیریت کنید.

اگرچه بهتر است در صورت امکان یک APK واحد برای پشتیبانی از همه دستگاه‌های هدف خود بسازید، اما این کار ممکن است به دلیل پشتیبانی فایل‌هایی از چندین رابط دودویی برنامه (ABI) منجر به یک APK بسیار بزرگ شود. یک راه برای کاهش اندازه APK شما، ایجاد چندین APK است که حاوی فایل‌هایی برای ABI های خاص باشند.

Gradle می‌تواند APKهای جداگانه‌ای ایجاد کند که فقط شامل کد و منابع خاص هر ABI هستند. این صفحه نحوه پیکربندی ساخت شما برای تولید چندین APK را شرح می‌دهد. اگر نیاز به ایجاد نسخه‌های مختلف از برنامه خود دارید که مبتنی بر ABI نیستند، به جای آن از build variants استفاده کنید.

ساخت خود را برای چندین APK پیکربندی کنید

برای پیکربندی ساخت خود برای چندین APK، یک بلوک splits به فایل build.gradle در سطح ماژول خود اضافه کنید. درون بلوک splits ، یک بلوک abi قرار دهید که مشخص کند Gradle چگونه می‌خواهد APKهای per-ABI تولید کند.

پیکربندی چندین APK برای ABIها

برای ایجاد APK های جداگانه برای ABI های مختلف، یک بلوک abi درون بلوک splits خود اضافه کنید. در بلوک abi خود، لیستی از ABI های مورد نظر را ارائه دهید.

گزینه‌های Gradle DSL زیر برای پیکربندی چندین APK به ازای هر ABI استفاده می‌شوند:

enable برای Groovy یا isEnable برای اسکریپت Kotlin
اگر این عنصر را روی true تنظیم کنید، Gradle بر اساس ABI هایی که تعریف می‌کنید، چندین APK تولید می‌کند. مقدار پیش‌فرض false است.
exclude
لیستی از ABIهایی که با کاما از هم جدا شده‌اند و نمی‌خواهید Gradle برای آنها APK جداگانه تولید کند را مشخص می‌کند. اگر می‌خواهید برای اکثر ABIها APK تولید کنید اما باید چند ABI که برنامه شما از آنها پشتیبانی نمی‌کند را حذف کنید، exclude .
reset()

لیست پیش‌فرض ABIها را پاک می‌کند. فقط زمانی استفاده می‌شود که با عنصر include ترکیب شود تا ABIهایی که می‌خواهید اضافه کنید را مشخص کند.

قطعه کد زیر با فراخوانی تابع reset() برای پاک کردن لیست، لیست ABI ها را فقط به x86 و x86_64 تنظیم می‌کند و سپس از 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های per-ABI، یک APK جهانی نیز تولید می‌کند. یک APK جهانی شامل کد و منابع برای همه ABIهای موجود در یک APK واحد است. مقدار پیش‌فرض آن false است.

مثال زیر برای هر ABI یک APK جداگانه تولید می‌کند: x86 و x86_64 . این کار با استفاده از reset() برای شروع با یک لیست خالی از ABIها انجام می‌شود و در ادامه لیستی از ABIهایی که هر کدام یک APK دریافت می‌کنند، include .

گرووی

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

کاتلین

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

برای پروژه‌هایی که کد native/C++ ندارند، پنل Build Variants دو ستون دارد: Module و Active Build Variant ، همانطور که در شکل ۱ نشان داده شده است.

پنل انواع ساخت
شکل ۱. پنل Build Variants دو ستون برای پروژه‌های بدون کد native/C++ دارد.

مقدار Active Build Variant برای ماژول، نوع ساختی را که مستقر شده و در ویرایشگر قابل مشاهده است، تعیین می‌کند. برای جابجایی بین انواع، روی سلول Active Build Variant برای یک ماژول کلیک کنید و نوع مورد نظر را از فیلد لیست انتخاب کنید.

پروژه‌هایی با کد نیتیو/++C

برای پروژه‌هایی با کد native/C++، پنل Build Variants دارای سه ستون است: Module ، Active Build Variant و Active ABI ، همانطور که در شکل 2 نشان داده شده است.

شکل ۲. پنل Build Variants ستون Active ABI را برای پروژه‌هایی با کد native/C++ اضافه می‌کند.

مقدار Active Build Variant برای ماژول، نوع ساختی را که مستقر شده و در ویرایشگر قابل مشاهده است، تعیین می‌کند. برای ماژول‌های بومی، مقدار Active ABI ، ABI مورد استفاده ویرایشگر را تعیین می‌کند، اما بر آنچه مستقر می‌شود تأثیری ندارد.

برای تغییر نوع ساخت یا ABI:

  1. روی سلول مربوط به ستون Active Build Variant یا Active ABI کلیک کنید.
  2. نوع یا ABI مورد نظر را از فیلد لیست انتخاب کنید. همگام‌سازی جدید به‌طور خودکار اجرا می‌شود.

تغییر هر یک از ستون‌های یک ماژول برنامه یا کتابخانه، تغییر را در تمام ردیف‌های وابسته اعمال می‌کند.

پیکربندی نسخه‌بندی

به طور پیش‌فرض، وقتی Gradle چندین APK تولید می‌کند، هر APK اطلاعات نسخه یکسانی دارد، همانطور که در فایل build.gradle یا build.gradle.kts در سطح ماژول مشخص شده است. از آنجا که فروشگاه Google Play اجازه نمی‌دهد چندین APK برای یک برنامه یکسان که همه اطلاعات نسخه یکسانی دارند، وجود داشته باشد، باید قبل از آپلود در فروشگاه Play مطمئن شوید که هر APK یک versionCode منحصر به فرد دارد.

شما می‌توانید فایل build.gradle در سطح ماژول خود را طوری پیکربندی کنید که versionCode برای هر APK نادیده بگیرد. با ایجاد یک نگاشت که برای هر ABI که چندین APK را برای آن پیکربندی می‌کنید، یک مقدار عددی منحصر به فرد اختصاص می‌دهد، می‌توانید کد نسخه خروجی را با مقداری که کد نسخه تعریف شده در بلوک defaultConfig یا productFlavors با مقدار عددی اختصاص داده شده به ABI ترکیب می‌کند، نادیده بگیرید.

در مثال زیر، APK مربوط به x86 ABI versionCode 2004 و x86_64 ABI versionCode 3004 را دریافت می‌کند.

اختصاص کدهای نسخه در گام‌های بزرگ، مانند ۱۰۰۰، به شما این امکان را می‌دهد که بعداً در صورت نیاز به به‌روزرسانی برنامه، کدهای نسخه منحصر به فردی اختصاص دهید. برای مثال، اگر defaultConfig.versionCode در به‌روزرسانی بعدی به ۵ برسد، Gradle versionCode ۲۰۰۵ را به x86 APK و ۳۰۰۵ را به x86_64 APK اختصاص می‌دهد.

نکته: اگر نسخه شما شامل یک APK جهانی است، به آن یک versionCode اختصاص دهید که پایین‌تر از versionCode سایر APKهای شما باشد. از آنجا که گوگل پلی استور نسخه‌ای از برنامه شما را نصب می‌کند که هم با دستگاه هدف سازگار است و هم بالاترین versionCode را دارد، اختصاص versionCode پایین‌تر به universal APK تضمین می‌کند که گوگل پلی استور قبل از بازگشت به universal APK، سعی می‌کند یکی از APKهای شما را نصب کند. کد نمونه زیر این کار را با نادیده نگرفتن versionCode پیش‌فرض یک universal 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]

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

کاتلین

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های مربوط به ماژول انتخاب شده فعلی در پنل Project ، روی Build > Build APK کلیک کنید. Gradle، APKهای مربوط به هر ABI را در دایرکتوری build/outputs/apk/ پروژه ایجاد می‌کند.

گریدل برای هر ABI که چندین APK را برای آن پیکربندی می‌کنید، یک APK می‌سازد.

برای مثال، قطعه کد build.gradle زیر امکان ساخت چندین APK برای ABI های x86 و x86_64 را فراهم می‌کند:

گرووی

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

کاتلین

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

خروجی پیکربندی نمونه شامل ۴ APK زیر است:

  • app-X86-release.apk : شامل کد و منابع برای x86 ABI است.
  • app-X86_64-release.apk : شامل کد و منابع برای x86_64 ABI است.

هنگام ساخت چندین APK بر اساس ABI، Gradle فقط در صورتی APK تولید می‌کند که کد و منابع مربوط به همه ABIها را داشته باشد که universalApk true در بلوک splits.abi در فایل build.gradle خود (برای Groovy) مقدار true یا 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 .