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

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

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

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

הגדרת ה-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.

האפשרויות הבאות של Gradle DSL משמשות להגדרת חבילות 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 לפי דחיסות, כחלופה מכשירים שלא תואמים ל-APKs עם תג <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")
        }
    }
}

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

הגדרה של כמה חבילות 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 בשביל גרוב, או 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++, בחלונית יצירת וריאנטים יש עמודות: מודול ו-Active Build וריאנט, כפי שמוצג באיור 1.

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

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

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

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

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

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

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

  1. לוחצים על התא ווריאנט Active Build. או Active 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 של ממשק ה-ABI של x86 מקבל versionCode של 2004 ואת ה-ABI של x86_64 מקבל versionCode של 3004.

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

טיפ: אם ה-build כולל APK אוניברסלי, מקצים לו versionCode שהוא נמוך יותר מזה של חבילות ה-APK האחרות שלך. כי חנות Google Play מתקינה את גרסת האפליקציה שהיא תואם למכשיר היעד ומתבסס על versionCode, מוקצית ערך נמוך יותר של versionCode חבילת APK אוניברסלית מבטיחה שחנות Google Play תנסה להתקין אחד חבילות APK לפני חזרה ל-APK האוניברסלי. הקוד לדוגמה הבא מטפל בכך על ידי לא ביטול ה-APK האוניברסלי ברירת המחדל היא versionCode.

מגניב

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

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

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

מגניב

...
  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 אוניברסלי שכולל קוד ומשאבים לכל צפיפות הנתונים, בנוסף ל-APKs לכל צפיפות.

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

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

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

modulename-screendensityABI-buildvariant.apk

רכיבי הסכימה הם:

modulename
מציין את שם המודול שנוצר.
screendensity
אם מופעלות כמה חבילות APK לדחיסות מסך, מציין את מסך צפיפות עבור ה-APK, כגון mdpi.
ABI

אם הופעלו כמה חבילות APK ל-ABI, מציין את ה-ABI של ה-APK, כמו בתור x86.

אם הופעלו כמה חבילות APK לדחיסות המסך ול-ABI, לדוגמה, Gradle משרשרת את שם הצפיפות לשם ה-ABI. mdpiX86 אם המדיניות universalApk מופעלת לכל ABI חבילות APK, Gradle משתמשת ב-universal כחלק ה-ABI ב-APK האוניברסלי [שם הקובץ].

buildvariant
מציין את הווריאנט של ה-build, למשל debug.

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