בניית חבילות APK מרובות

אזהרה: החל מאוגוסט 2021, חובה לפרסם את כל האפליקציות החדשות כ-App Bundles. אם אתם מפרסמים את האפליקציה ב-Google Play, עליכם ליצור קובץ Android App Bundle ולהעלות אותו. כשמפרסמים כמה חבילות APK, Google Play יוצרת אותן באופן אוטומטי ומציגה אותן עם אופטימיזציה בהתאם להגדרות המכשיר של כל משתמש, כך שהם מורידים רק את הקוד והמשאבים הנדרשים להפעלת האפליקציה. כדאי לפרסם כמה חבילות APK אם מפרסמים בחנות שלא תומכת בפורמט AAB. במקרה כזה, תצטרכו ליצור, לחתום ולנהל כל קובץ APK בעצמכם.

מומלץ ליצור קובץ APK יחיד שתומך בכל מכשירי היעד שלכם, כשהדבר אפשרי. עם זאת, יכול להיות שהקובץ יהיה גדול מאוד בגלל קבצים שתומכים במספר דחיסות מסך או ממשקי Application Binary (ABI). אחת הדרכים להקטין את גודל ה-APK היא ליצור חבילות APK מרובות שמכילות קבצים לצפיפות מסך ספציפית או ל-ABI ספציפי.

Gradle יכול ליצור קובצי APK נפרדים שמכילים רק קוד ומשאבים ספציפיים לכל צפיפות או ABI. בדף הזה מוסבר איך להגדיר את ה-build כך שייצור כמה חבילות APK. אם אתם צריכים ליצור גרסאות שונות של האפליקציה שלא מבוססות על צפיפות המסך או על ABI, תוכלו להשתמש במקום זאת בוריאנטים של גרסאות build.

הגדרת ה-build ליצירת כמה חבילות APK

כדי להגדיר את ה-build ליצירת כמה חבילות APK, מוסיפים בלוק splits לקובץ build.gradle ברמת המודול. בתוך הבלוק splits, מציינים בלוק density שמציין איך רוצים ש-Gradle תיצור קובצי APK לפי צפיפות, או בלוק abi שמציין איך רוצים ש-Gradle תיצור קובצי APK לפי ABI. אפשר לספק גם בלוקים של צפיפות וגם בלוקים של ABI, ומערכת ה-build תיצור קובץ APK לכל שילוב של צפיפות ו-ABI.

הגדרת כמה חבילות APK לדחיסות מסך שונות

כדי ליצור חבילות APK נפרדות לדחיסות מסך שונות, מוסיפים בלוק density בתוך הבלוק splits. בבלוק density, מציינים רשימה של צפיפות המסך הרצויה ושל גדלי המסך התואמים. מומלץ להשתמש ברשימת גודלי המסך התואמים רק אם אתם צריכים להוסיף רכיבים ספציפיים של <compatible-screens> לכל מניפסט של APK.

אפשרויות ה-DSL הבאות של Gradle משמשות להגדרת כמה חבילות APK לדחיסות מסך שונות:

enable עבור Groovy, ‏ 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 כדי להסיר את שלוש הצפיפויות האלה מרשימת ברירת המחדל של כל הצפיפויות.

Groovy

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

למידע נוסף על התאמה אישית של גרסאות build שונות של האפליקציה לסוגים ספציפיים של מסכים ומכשירים, ראו הצהרה על תמיכה מוגבלת במסכים.

הגדרת כמה חבילות APK לממשקי ABI

כדי ליצור חבילות APK נפרדות לממשקי ABI שונים, מוסיפים בלוק abi בתוך הבלוק splits. בבלוק abi, מספקים רשימה של ממשקי ABI הרצויים.

האפשרויות הבאות של Gradle DSL משמשות להגדרת כמה חבילות APK לכל ABI:

enable עבור Groovy או isEnable עבור סקריפט Kotlin
אם מגדירים את האלמנט הזה ל-true, Gradle יוצר כמה חבילות APK על סמך ה-ABIs שתגדירו. ערך ברירת המחדל הוא false.
exclude
מציינת רשימה של ABIs מופרדים בפסיקים שלא רוצים ש-Gradle תיצור להם חבילות APK נפרדות. משתמשים ב-exclude אם רוצים ליצור חבילות APK לרוב ממשקי ה-ABI, אבל צריך להחריג כמה ממשקי ABI שהאפליקציה לא תומכת בהם.
reset()

ניקוי רשימת ברירת המחדל של ABI. משתמשים ב-include רק בשילוב עם הרכיב include כדי לציין את ה-ABIs שרוצים להוסיף.

קטע הקוד הבא מגדיר את רשימת ה-ABIs רק ל-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
מציינת רשימה של ABIs שמופרדים בפסיקים שעבורם רוצים ש-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.

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

רשימת ממשקי ה-ABI הנתמכים מופיעה במאמר ממשקי ABI נתמכים.

פרויקטים ללא קוד מקורי/C++

בפרויקטים ללא קוד מקורי או קוד ב-C++‎, בחלונית Build Variants יש שתי עמודות: Module ו-Active Build Variant, כפי שמוצג באיור 1.

החלונית &#39;וריאנטים של build&#39;
איור 1. בחלונית Build Variants יש שתי עמודות לפרויקטים ללא קוד מקורי/C++.

הערך של Active Build Variant (גרסת build פעילה) של המודול קובע את גרסת ה-build שנפרסת ומוצגת בעורך. כדי לעבור בין הווריאנטים, לוחצים על התא Active Build Variant של המודול ובוחרים את הווריאנט הרצוי משדה הרשימה.

פרויקטים עם קוד מקורי/C++

בפרויקטים עם קוד מקורי/C++‏, בחלונית Build Variants יש שלוש עמודות: Module,‏ Active Build Variant ו-Active ABI, כפי שמוצג באיור 2.

איור 2. בחלונית Build Variants נוספת העמודה Active ABI לפרויקטים עם קוד מקורי/C++.

הערך של Active Build Variant (גרסת build פעילה) לקובץ המודול קובע את גרסת ה-build שנפרסת ומוצגת בעורך. במודולים מקומיים, הערך של Active ABI קובע את ה-ABI שבו עושה שימוש העורך, אבל הוא לא משפיע על מה שנפרס.

כדי לשנות את סוג ה-build או את ה-ABI:

  1. לוחצים על התא של העמודה Active Build Variant או Active ABI.
  2. בוחרים את הווריאנט או את ה-ABI הרצוי בשדה הרשימה. סנכרון חדש יתבצע באופן אוטומטי.

שינוי של אחת מהעמודות באפליקציה או במודול הספרייה מחיל את השינוי על כל השורות התלויות.

הגדרת ניהול גרסאות

כברירת מחדל, כש-Gradle יוצר כמה חבילות APK, לכל אחת מהן יש את אותם פרטי גרסה, כפי שצוינו בקובץ build.gradle או build.gradle.kts ברמת המודול. חנות Google Play לא מאפשרת להעלות כמה קובצי APK של אותה אפליקציה עם אותם פרטי גרסה, לכן עליכם לוודא שלכל קובץ APK יש versionCode ייחודי לפני ההעלאה ל-Play Store.

אפשר להגדיר את קובץ build.gradle ברמת המודול כדי לשנות את versionCode לכל קובץ APK. אם יוצרים מיפוי שמקצה ערך מספרי ייחודי לכל ABI וצפיפות שמגדירים עבור כמה חבילות APK, אפשר לשנות את קוד הגרסה של הפלט בערך שמציג שילוב של קוד הגרסה שמוגדר בבלוק defaultConfig או productFlavors עם הערך המספרי שהוקצה לצפיפות או ל-ABI.

בדוגמה הבאה, ה-APK של ה-ABI‏ x86 מקבל versionCode של 2004, וה-ABI‏ x86_64 מקבל versionCode של 3004.

הקצאת קודי גרסה במרווחים גדולים, כמו 1,000, מאפשרת לכם להקצות מאוחר יותר קודי גרסה ייחודיים אם תצטרכו לעדכן את האפליקציה. לדוגמה, אם defaultConfig.versionCode חוזר על עצמו עד 5 בעדכון מאוחר יותר, Gradle מקצה ל-x86 APK את הערך 2005 ול-x86_64 APK את הערך 3005.versionCode

טיפ: אם הגרסה שלכם כוללת 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]

// 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, לוחצים על Build (יצירת גרסה) > Build APK (יצירת קובץ APK) כדי ליצור את כל חבילות ה-APK של המודול שנבחר כרגע בחלונית Project. Gradle יוצר את חבילות ה-APK לכל צפיפות או ABI בתיקייה build/outputs/apk/ של הפרויקט.

Gradle יוצר קובץ APK לכל צפיפות או ABI שאתם מגדירים עבור כמה חבילות APK. אם מפעילים כמה חבילות APK גם לדחיסות וגם ל-ABI, Gradle יוצר חבילה לכל שילוב של דחיסות ו-ABI.

לדוגמה, קטע הקוד build.gradle הבא מאפשר ליצור כמה קובצי APK לצפיפות mdpi ו-hdpi, וגם ל-ABI‏ x86 ו-x86_64:

Groovy

...
  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")
    }
  }

הפלט של ההגדרה לדוגמה כולל את 4 קובצי ה-APK הבאים:

  • app-hdpiX86-release.apk: מכיל קוד ומשאבים לצפיפות hdpi ול-ABI של x86.
  • app-hdpiX86_64-release.apk: מכיל קוד ומשאבים לצפיפות hdpi ול-ABI של x86_64.
  • app-mdpiX86-release.apk: מכיל קוד ומשאבים לצפיפות mdpi ול-ABI של x86.
  • app-mdpiX86_64-release.apk: מכיל קוד ומשאבים לצפיפות mdpi ול-ABI של x86_64.

כשמפתחים כמה חבילות APK על סמך צפיפות המסך, Gradle יוצר תמיד חבילה אוניברסלית של APK שכוללת קוד ומשאבים לכל הצפיפויות, בנוסף לחבילות ה-APK לכל צפיפות.

כשמפתחים כמה קובצי APK על סמך ABI, Gradle יוצר קובץ APK שכולל קוד ומשאבים לכל ה-ABIs רק אם מציינים את הערך universalApk true בבלוק splits.abi בקובץ build.gradle (ב-Groovy) או את הערך isUniversalApk = true בבלוק splits.abi בקובץ build.gradle.kts (ב-Kotlin script).

הפורמט של שם קובץ ה-APK

כשמפתחים כמה חבילות APK, Gradle יוצר את שמות הקבצים שלהן לפי התבנית הבאה:

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
מציין את וריאנט ה-build שנוצר, למשל debug.

לדוגמה, כשיוצרים קובץ APK עם דחיסות מסך של mdpi לגרסת ניפוי הבאגים של myApp, שם קובץ ה-APK הוא myApp-mdpi-debug.apk. שם הקובץ של גרסת המהדורה של myApp שמוגדרת ליצירת כמה חבילות APK גם לדחיסות המסך mdpi וגם ל-ABI‏ x86 הוא myApp-mdpiX86-release.apk.