בדף הזה מוסבר איך להגדיר וריאציות של build כדי ליצור גרסאות שונות של האפליקציה מתוך פרויקט יחיד, ואיך לנהל בצורה נכונה את התלות ואת הגדרות החתימה.
כל וריאציה של build מייצגת גרסה שונה של האפליקציה שאפשר ליצור. לדוגמה, יכול להיות שתרצו ליצור גרסה אחת של האפליקציה בחינם עם תוכן מוגבל, וגרסה בתשלום עם תוכן נוסף. אפשר גם ליצור גרסאות שונות של האפליקציה שמיועדות למכשירים שונים, על סמך רמת ה-API או וריאציות אחרות של המכשיר.
גרסאות build הן תוצאה של שימוש ב-Gradle בקבוצה ספציפית של כללים לשילוב הגדרות, קוד ומשאבים שהוגדרו בסוגי ה-build ובטעמי המוצר. למרות שאתם לא מגדירים ישירות וריאציות של גרסאות build, אתם מגדירים את סוגי ה-build ואת טעמי המוצרים שמרכיבים אותן.
לדוגמה, טעם מוצר מסוג 'הדגמה' עשוי לציין תכונות מסוימות ודרישות מכשיר, כמו קוד מקור מותאם אישית, משאבים ורמות API מינימליות, בעוד שסוג בנייה מסוג 'ניפוי באגים' חל על הגדרות שונות של בנייה ואריזה, כמו אפשרויות ניפוי באגים ומפתחות חתימה. וריאנט הבנייה שמשלב את שניהם הוא הגרסה demoDebug של האפליקציה, והוא כולל שילוב של ההגדרות והמשאבים שנכללים בטעם המוצר demo, בסוג הבנייה debug ובקבוצת המקורות main/
.
הגדרת סוגי build
אפשר ליצור ולהגדיר סוגי build בתוך הבלוק android
בקובץ build.gradle.kts
ברמת המודול. כשיוצרים מודול חדש, Android Studio יוצר באופן אוטומטי את סוגי ה-build של ניפוי הבאגים והגרסה. למרות שסוג ה-build של ניפוי הבאגים לא מופיע בקובץ הגדרות ה-build, Android Studio מגדיר אותו עם debuggable
true
. כך תוכלו לנפות באגים באפליקציה במכשירי Android מאובטחים, ולהגדיר חתימת אפליקציה באמצעות מאגר מפתחות כללי לניפוי באגים.
אפשר להוסיף את סוג ה-build של ניפוי הבאגים להגדרה אם רוצים להוסיף או לשנות הגדרות מסוימות. בדוגמה הבאה מצוין
applicationIdSuffix
לסוג ה-build של ניפוי הבאגים, ומוגדר סוג ה-build 'staging' שמופעל באמצעות ההגדרות מסוג ה-build של ניפוי הבאגים:
Kotlin
android { defaultConfig { manifestPlaceholders["hostName"] = "www.example.com" ... } buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } getByName("debug") { applicationIdSuffix = ".debug" isDebuggable = true } /** * The `initWith` property lets you copy configurations from other build types, * then configure only the settings you want to change. This one copies the debug build * type, and then changes the manifest placeholder and application ID. */ create("staging") { initWith(getByName("debug")) manifestPlaceholders["hostName"] = "internal.example.com" applicationIdSuffix = ".debugStaging" } } }
מגניב
android { defaultConfig { manifestPlaceholders = [hostName:"www.example.com"] ... } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { applicationIdSuffix ".debug" debuggable true } /** * The `initWith` property lets you copy configurations from other build types, * then configure only the settings you want to change. This one copies the debug build * type, and then changes the manifest placeholder and application ID. */ staging { initWith debug manifestPlaceholders = [hostName:"internal.example.com"] applicationIdSuffix ".debugStaging" } } }
הערה: כשמבצעים שינויים בקובץ הגדרות ה-build, צריך לסנכרן את הפרויקט עם ההגדרות החדשות ב-Android Studio. כדי לסנכרן את הפרויקט, לוחצים על סנכרון עכשיו בסרגל ההתראות שמופיע כשמבצעים שינוי או לוחצים על סנכרון הפרויקט בסרגל הכלים. אם Android Studio מזהה שגיאות בהגדרה, חלון Messages מופיע עם תיאור של הבעיה.
מידע נוסף על כל הנכסים שאפשר להגדיר באמצעות סוגי build זמין במאמר בנושא
BuildType
.
הגדרת גרסאות מוצר
יצירת גרסאות מוצר דומה ליצירת סוגי build. מוסיפים את טעמי המוצרים לבלוק productFlavors
בהגדרות הבנייה וכוללים את ההגדרות הרצויות.
המאפיינים של טעמי המוצרים זהים למאפיינים של defaultConfig
, כי defaultConfig
שייך למעשה למחלקה
ProductFlavor
. כלומר, אפשר לספק את הגדרות הבסיס לכל הטעמים בבלוק defaultConfig
, וכל טעם יכול לשנות כל אחד מערכי ברירת המחדל האלה, כמו applicationId
. מידע נוסף על מזהה האפליקציה זמין במאמר הגדרת מזהה האפליקציה.
הערה: עדיין צריך לציין שם חבילה באמצעות המאפיין package
בקובץ המניפסט main/
. אתם צריכים להשתמש בשם החבילה הזה גם בקוד המקור כדי להתייחס למחלקה R
או כדי לפתור בעיות שקשורות לרישום של פעילות או שירותים יחסיים. כך תוכלו להשתמש ב-applicationId
כדי לתת לכל טעם של מוצר מזהה ייחודי לאריזה ולהפצה, בלי שתצטרכו לשנות את קוד המקור.
כל הטעמים צריכים להיות שייכים למאפיין טעם עם שם, שהוא קבוצה של טעמי מוצרים. צריך להקצות את כל הטעמים למאפיין הטעם; אחרת, תוצג שגיאת הבנייה הבאה.
Error: All flavors must now belong to a named flavor dimension. The flavor 'flavor_name' is not assigned to a flavor dimension.
אם מודול מסוים מציין רק מאפיין טעם אחד, התוסף Android Gradle מקצה באופן אוטומטי את כל הטעמים של המודול למאפיין הזה.
בדוגמת הקוד הבאה נוצר מאפיין טעם בשם version, ונוספים לו טעמי המוצר demo ו-full. לכל אחת מהגרסאות האלה יש
applicationIdSuffix
משלה וגם versionNameSuffix
:
Kotlin
android { ... defaultConfig {...} buildTypes { getByName("debug"){...} getByName("release"){...} } // Specifies one flavor dimension. flavorDimensions += "version" productFlavors { create("demo") { // Assigns this product flavor to the "version" flavor dimension. // If you are using only one dimension, this property is optional, // and the plugin automatically assigns all the module's flavors to // that dimension. dimension = "version" applicationIdSuffix = ".demo" versionNameSuffix = "-demo" } create("full") { dimension = "version" applicationIdSuffix = ".full" versionNameSuffix = "-full" } } }
מגניב
android { ... defaultConfig {...} buildTypes { debug{...} release{...} } // Specifies one flavor dimension. flavorDimensions "version" productFlavors { demo { // Assigns this product flavor to the "version" flavor dimension. // If you are using only one dimension, this property is optional, // and the plugin automatically assigns all the module's flavors to // that dimension. dimension "version" applicationIdSuffix ".demo" versionNameSuffix "-demo" } full { dimension "version" applicationIdSuffix ".full" versionNameSuffix "-full" } } }
הערה: אם יש לכם אפליקציה מדור קודם (שנוצרה לפני אוגוסט 2021) שאתם מפיצים באמצעות קובצי APK ב-Google Play, כדי להפיץ את האפליקציה באמצעות תמיכה בכמה קובצי APK ב-Google Play, צריך להקצות את אותו ערך applicationId
לכל הווריאציות ולתת לכל וריאציה ערך versionCode
שונה. כדי להפיץ וריאציות שונות של האפליקציה כאפליקציות נפרדות ב-Google Play, צריך להקצות applicationId
שונה לכל וריאציה.
אחרי שיוצרים ומגדירים את טעמי המוצרים, לוחצים על סנכרון
עכשיו בסרגל ההודעות. אחרי שהסנכרון מסתיים, Gradle יוצר באופן אוטומטי וריאציות של build על סמך סוגי ה-build והטעמים של המוצרים, ונותן להם שמות בהתאם ל-<product-flavor><Build-Type>
. לדוגמה, אם יצרתם טעמי מוצר בשם demo ו-full, והשארתם את סוגי ה-build שמוגדרים כברירת מחדל debug ו-release, Gradle יוצר את וריאציות ה-build הבאות:
-
demoDebug
-
demoRelease
-
fullDebug
-
fullRelease
כדי לבחור את הווריאנט של הגרסה שרוצים ליצור ולהריץ, עוברים אל Build (גרסה) > Select Build Variant (בחירת וריאנט של גרסה) ובוחרים וריאנט של גרסה מהתפריט. כדי להתחיל להתאים אישית כל וריאציה של בנייה עם תכונות ומשאבים משלה, צריך ליצור ולנהל קבוצות של מקורות, כמו שמתואר בדף הזה.
שינוי מזהה האפליקציה עבור וריאציות של גרסאות Build
כשיוצרים APK או AAB לאפליקציה, כלי הבנייה מתייגים את האפליקציה עם מזהה האפליקציה שמוגדר בבלוק defaultConfig
מהקובץ build.gradle.kts
, כמו בדוגמה הבאה. עם זאת, אם רוצים ליצור גרסאות שונות של האפליקציה שיופיעו כרשימות נפרדות בחנות Google Play, למשל גרסה 'חינמית' וגרסת 'פרו', צריך ליצור וריאציות נפרדות של גרסת ה-build, שלכל אחת מהן יש מזהה אפליקציה שונה.
במקרה כזה, צריך להגדיר כל וריאציית build כטעם מוצר נפרד. לכל טעם בתוך הבלוק productFlavors
, אפשר להגדיר מחדש את המאפיין applicationId
, או להוסיף פלח למזהה האפליקציה שמוגדר כברירת מחדל באמצעות applicationIdSuffix
, כמו שמוצג כאן:
Kotlin
android { defaultConfig { applicationId = "com.example.myapp" } productFlavors { create("free") { applicationIdSuffix = ".free" } create("pro") { applicationIdSuffix = ".pro" } } }
מגניב
android { defaultConfig { applicationId "com.example.myapp" } productFlavors { free { applicationIdSuffix ".free" } pro { applicationIdSuffix ".pro" } } }
כך, מזהה האפליקציה של גרסת המוצר 'free' הוא com.example.myapp.free.
אפשר גם להשתמש ב-applicationIdSuffix
כדי להוסיף פלח על סמך סוג ה-build, כמו שמוצג כאן:
Kotlin
android { ... buildTypes { getByName("debug") { applicationIdSuffix = ".debug" } } }
מגניב
android { ... buildTypes { debug { applicationIdSuffix ".debug" } } }
מכיוון ש-Gradle מחיל את ההגדרה של סוג ה-build אחרי טעם המוצר, מזהה האפליקציה של וריאציית ה-build 'free debug' הוא 'com.example.myapp.free.debug'. האפשרות הזו שימושית אם רוצים שגם גרסת הניפוי באגים וגם גרסת ההפצה יהיו מותקנות באותו מכשיר, כי לשתי אפליקציות לא יכול להיות אותו מזהה אפליקציה.
אם יש לכם אפליקציה מדור קודם (שנוצרה לפני אוגוסט 2021) שאתם מפיצים באמצעות קובצי APK ב-Google Play, ואתם רוצים להשתמש באותו דף אפליקציה כדי להפיץ כמה קובצי APK שכל אחד מהם מיועד להגדרת מכשיר שונה, כמו רמת ה-API, אתם צריכים להשתמש באותו מזהה אפליקציה לכל וריאציה של בנייה, אבל לתת לכל קובץ APKversionCode
שונה. מידע נוסף זמין במאמר בנושא תמיכה בכמה קובצי APK. הפרסום באמצעות חבילות AAB לא מושפע, כי נעשה בו שימוש בארטיפקט יחיד שמשתמש בקוד גרסה יחיד ובמזהה אפליקציה יחיד כברירת מחדל.
טיפ: אם אתם צריכים להפנות למזהה האפליקציה בקובץ המניפסט, אתם יכולים להשתמש ב-placeholder ${applicationId}
בכל מאפיין של המניפסט. במהלך בנייה, Gradle מחליף את התג הזה במזהה האפליקציה בפועל. מידע נוסף זמין במאמר בנושא הוספת משתני build למניפסט.
שילוב של כמה טעמים של מוצרים עם מימדי טעם
במקרים מסוימים, יכול להיות שתרצו לשלב הגדרות מכמה גרסאות של מוצר. לדוגמה, יכול להיות שתרצו ליצור הגדרות שונות לטעמי המוצר 'מלא' ו'הדגמה' שמבוססים על רמת ה-API. כדי לעשות את זה, התוסף Android Gradle מאפשר ליצור כמה קבוצות של טעמי מוצר כמאפייני טעם.
כשמפתחים את האפליקציה, Gradle משלב הגדרה של טעם מוצר מכל מימד טעם שמגדירים, יחד עם הגדרה של סוג build, כדי ליצור את וריאציית ה-build הסופית. Gradle לא משלב טעמי מוצר ששייכים לאותו מאפיין טעם.
בדוגמת הקוד הבאה נעשה שימוש במאפיין
flavorDimensions
כדי ליצור מאפיין של טעם 'mode' כדי לקבץ את הטעמים של המוצר 'full' ו-'demo', ומאפיין של טעם 'api' כדי לקבץ את ההגדרות של טעמי המוצר על סמך רמת ה-API:
Kotlin
android { ... buildTypes { getByName("debug") {...} getByName("release") {...} } // Specifies the flavor dimensions you want to use. The order in which you // list the dimensions determines their priority, from highest to lowest, // when Gradle merges variant sources and configurations. You must assign // each product flavor you configure to one of the flavor dimensions. flavorDimensions += listOf("api", "mode") productFlavors { create("demo") { // Assigns this product flavor to the "mode" flavor dimension. dimension = "mode" ... } create("full") { dimension = "mode" ... } // Configurations in the "api" product flavors override those in "mode" // flavors and the defaultConfig block. Gradle determines the priority // between flavor dimensions based on the order in which they appear next // to the flavorDimensions property, with the first dimension having a higher // priority than the second, and so on. create("minApi24") { dimension = "api" minSdk = 24 // To ensure the target device receives the version of the app with // the highest compatible API level, assign version codes in increasing // value with API level. versionCode = 30000 + (android.defaultConfig.versionCode ?: 0) versionNameSuffix = "-minApi24" ... } create("minApi23") { dimension = "api" minSdk = 23 versionCode = 20000 + (android.defaultConfig.versionCode ?: 0) versionNameSuffix = "-minApi23" ... } create("minApi21") { dimension = "api" minSdk = 21 versionCode = 10000 + (android.defaultConfig.versionCode ?: 0) versionNameSuffix = "-minApi21" ... } } } ...
מגניב
android { ... buildTypes { debug {...} release {...} } // Specifies the flavor dimensions you want to use. The order in which you // list the dimensions determines their priority, from highest to lowest, // when Gradle merges variant sources and configurations. You must assign // each product flavor you configure to one of the flavor dimensions. flavorDimensions "api", "mode" productFlavors { demo { // Assigns this product flavor to the "mode" flavor dimension. dimension "mode" ... } full { dimension "mode" ... } // Configurations in the "api" product flavors override those in "mode" // flavors and the defaultConfig block. Gradle determines the priority // between flavor dimensions based on the order in which they appear next // to the flavorDimensions property, with the first dimension having a higher // priority than the second, and so on. minApi24 { dimension "api" minSdkVersion 24 // To ensure the target device receives the version of the app with // the highest compatible API level, assign version codes in increasing // value with API level. versionCode 30000 + android.defaultConfig.versionCode versionNameSuffix "-minApi24" ... } minApi23 { dimension "api" minSdkVersion 23 versionCode 20000 + android.defaultConfig.versionCode versionNameSuffix "-minApi23" ... } minApi21 { dimension "api" minSdkVersion 21 versionCode 10000 + android.defaultConfig.versionCode versionNameSuffix "-minApi21" ... } } } ...
מספר וריאציות ה-Build ש-Gradle יוצר שווה למכפלה של מספר הטעמים בכל מאפיין טעם ומספר סוגי ה-Build שהגדרתם. כש-Gradle נותן שם לכל וריאנט של build או לארטיפקטים תואמים, טעמי המוצר ששייכים למאפיין טעם עם עדיפות גבוהה יותר מופיעים ראשונים, ואחריהם אלה ששייכים למאפיינים עם עדיפות נמוכה יותר, ואז סוג ה-build.
בדוגמה של הגדרת ה-build הקודמת, Gradle יוצר 12 וריאציות build בסך הכול, עם סכמת השמות הבאה:
- וריאנט build:
[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
- קובץ ה-APK התואם:
app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
- לדוגמה,
- Build variant:
minApi24DemoDebug
- Corresponding APK:
app-minApi24-demo-debug.apk
בנוסף לספריות של קבוצות מקורות שאפשר ליצור לכל טעם מוצר וגרסת build, אפשר גם ליצור ספריות של קבוצות מקורות לכל שילוב של טעמי מוצר. לדוגמה, אפשר ליצור מקורות Java ולהוסיף אותם לספרייה src/demoMinApi24/java/
, ו-Gradle ישתמש במקורות האלה רק כשיוצרים וריאציה שמשלבת בין שני טעמי המוצר האלה.
למערכי מקורות שיוצרים לשילובי טעמים של מוצרים יש עדיפות גבוהה יותר מאשר למערכי מקורות ששייכים לכל טעם מוצר בנפרד. כדי לקבל מידע נוסף על קבוצות מקור ועל האופן שבו Gradle ממזג משאבים, אפשר לקרוא את הקטע בנושא יצירת קבוצות מקור.
סינון וריאציות
Gradle יוצר וריאציית build לכל שילוב אפשרי של טעמי המוצר וסוגי ה-build שהגדרתם. עם זאת, יכול להיות שיש וריאציות מסוימות של build שלא נחוצות לכם או שלא מתאימות להקשר של הפרויקט שלכם. כדי להסיר הגדרות מסוימות של וריאציות build, יוצרים מסנן וריאציות בקובץ build.gradle.kts
ברמת המודול.
בדוגמה הבאה, שמתבססת על הגדרת ה-build מהקטע הקודם, נניח שאתם מתכננים לתמוך רק ברמות API 23 ומעלה בגרסת ההדגמה של האפליקציה. אתם יכולים להשתמש בבלוק
variantFilter
כדי לסנן את כל ההגדרות של וריאציות ה-build שמשלבות בין מאפייני המוצר minApi21 ו-demo:
Kotlin
android { ... buildTypes {...} flavorDimensions += listOf("api", "mode") productFlavors { create("demo") {...} create("full") {...} create("minApi24") {...} create("minApi23") {...} create("minApi21") {...} } } androidComponents { beforeVariants { variantBuilder -> // To check for a certain build type, use variantBuilder.buildType == "<buildType>" if (variantBuilder.productFlavors.containsAll(listOf("api" to "minApi21", "mode" to "demo"))) { // Gradle ignores any variants that satisfy the conditions above. variantBuilder.enable = false } } } ...
מגניב
android { ... buildTypes {...} flavorDimensions "api", "mode" productFlavors { demo {...} full {...} minApi24 {...} minApi23 {...} minApi21 {...} } variantFilter { variant -> def names = variant.flavors*.name // To check for a certain build type, use variant.buildType.name == "<buildType>" if (names.contains("minApi21") && names.contains("demo")) { // Gradle ignores any variants that satisfy the conditions above. setIgnore(true) } } } ...
אחרי שמוסיפים מסנן וריאציות להגדרת ה-build ולוחצים על Sync Now בסרגל ההתראות, מערכת Gradle מתעלמת מכל וריאציות ה-build שעומדות בתנאים שצוינו. הווריאציות של הגרסה לא מופיעות יותר בתפריט כשלוחצים על Build > Select Build Variant בסרגל התפריטים או על Build Variants בסרגל של חלון הכלים.
יצירת קבוצות של מקורות
כברירת מחדל, Android Studio יוצר main/
source set וספריות לכל מה שרוצים לשתף בין כל וריאציות ה-build. עם זאת, אתם יכולים ליצור קבוצות חדשות של מקורות כדי לשלוט בדיוק באילו קבצים Gradle מבצע קומפילציה ואילו קבצים הוא אורז עבור סוגי build ספציפיים, טעמי מוצר, שילובים של טעמי מוצר (כשמשתמשים במאפייני טעם) ווריאציות של build.
לדוגמה, אתם יכולים להגדיר פונקציונליות בסיסית בערכת המקור main/
ולהשתמש בערכות מקור של טעמי מוצר כדי לשנות את המיתוג של האפליקציה עבור לקוחות שונים, או לכלול הרשאות מיוחדות ופונקציונליות של רישום ביומן רק עבור וריאציות של גרסאות build שמשתמשות בסוג debug build.
מערכת Gradle מצפה שקבצים וספריות של קבוצת מקורות יהיו מאורגנים בצורה מסוימת, בדומה לקבוצת המקורות main/
. לדוגמה, Gradle מצפה שקובצי מחלקות של Kotlin או Java שספציפיים לסוג ה-build של debug ימוקמו בספריות src/debug/kotlin/
או src/debug/java/
.
פלאגין Android Gradle מספק משימת Gradle שימושית שמראה לכם איך לארגן את הקבצים לכל אחד מסוגי הבנייה, טעמי המוצר וגרסאות הבנייה. לדוגמה, בדוגמה הבאה מתוך פלט המשימה מוסבר איפה Gradle מצפה למצוא קבצים מסוימים עבור סוג ה-build debug:
------------------------------------------------------------ Project :app ------------------------------------------------------------ ... debug ---- Compile configuration: debugCompile build.gradle name: android.sourceSets.debug Java sources: [app/src/debug/java] Kotlin sources: [app/src/debug/kotlin, app/src/debug/java] Manifest file: app/src/debug/AndroidManifest.xml Android resources: [app/src/debug/res] Assets: [app/src/debug/assets] AIDL sources: [app/src/debug/aidl] RenderScript sources: [app/src/debug/rs] JNI sources: [app/src/debug/jni] JNI libraries: [app/src/debug/jniLibs] Java-style resources: [app/src/debug/resources]
כדי לראות את הפלט הזה, מבצעים את הפעולות הבאות:
- לוחצים על Gradle בסרגל חלון הכלים.
עוברים אל MyApplication > Tasks > android ולוחצים לחיצה כפולה על sourceSets.
כדי לראות את התיקייה Tasks, צריך לאפשר ל-Gradle ליצור את רשימת המשימות במהלך הסנכרון. לשם כך, בצע את הצעדים הבאים:
- לוחצים על File > Settings > Experimental (Android Studio > Settings > Experimental ב-macOS).
- מבטלים את הסימון של האפשרות Do not build Gradle task list during Gradle sync.
- אחרי ש-Gradle מריץ את המשימה, נפתח החלון Run ומוצג הפלט.
הערה: בפלט של המשימה מוצג גם איך לארגן קבוצות של קבצי מקור לקבצים שרוצים להשתמש בהם כדי להריץ בדיקות לאפליקציה, כמו test/
ו-androidTest/
קבוצות של קבצי מקור לבדיקה.
כשיוצרים וריאנט חדש של build, Android Studio לא יוצר את ספריות קבוצת המקור בשבילכם, אבל הוא מספק כמה אפשרויות שיעזרו לכם. לדוגמה, כדי ליצור רק את הספרייה java/
לסוג הבנייה debug:
- פותחים את החלונית Project ובוחרים בתצוגה Project מהתפריט שבראש החלונית.
- נווט אל
MyProject/app/src/
. - לוחצים לחיצה ימנית על הספרייה
src
ובוחרים באפשרות חדש > ספרייה. - בתפריט שמתחת לGradle Source Sets, בוחרים באפשרות full/java.
- מקישים על Enter.
Android Studio יוצר ספרייה של קבוצת קבצי מקור לסוג ה-build של ניפוי הבאגים, ואז יוצר בתוכה את הספרייה java/
. לחלופין, אפשר להוסיף קובץ חדש לפרויקט עבור וריאציית build ספציפית, ו-Android Studio ייצור את הספריות בשבילכם.
לדוגמה, כדי ליצור קובץ XML של ערכים לסוג ה-build 'debug':
- בחלונית Project, לוחצים לחיצה ימנית על הספרייה
src
ובוחרים באפשרות New (חדש) > XML > Values XML File (קובץ XML של ערכים). - מזינים שם לקובץ ה-XML או משאירים את שם ברירת המחדל.
- בתפריט שליד Target Source Set (קבוצת מקורות יעד), בוחרים באפשרות debug (ניפוי באגים).
- לוחצים על סיום.
מכיוון שסוג ה-build 'debug' צוין כמערך המקורות של היעד, Android Studio יוצר באופן אוטומטי את הספריות הנדרשות כשהוא יוצר את קובץ ה-XML. מבנה הספריות שמתקבל נראה כמו באיור 1.

איור 1. ספריות חדשות של קבוצות מקורות לסוג הבנייה debug.
בסמל של קבוצות מקור פעילות מופיע סימון ירוק שמציין שהן פעילות.
debug
קבוצת המקורות מסומנת בסיומת [main]
כדי לציין שהיא תמוזג
עם קבוצת המקורות main
.
באותו אופן, אפשר ליצור ספריות של קבוצות מקור לטעמים של מוצרים, כמו src/demo/
, ולגרסאות build, כמו src/demoDebug/
. בנוסף, אפשר ליצור קבוצות של מקורות בדיקה שמטרגטות וריאנטים ספציפיים של בנייה, כמו src/androidTestDemoDebug/
. מידע נוסף זמין במאמר בנושא בדיקת קבוצות של מקורות.
שינוי ההגדרות של קבוצת ברירת המחדל של מקורות
אם יש לכם מקורות שלא מאורגנים במבנה הקובץ של קבוצת המקורות שמוגדרת כברירת מחדל, ש-Gradle מצפה לו, כמו שמתואר בקטע הקודם בנושא יצירת קבוצות מקורות, אתם יכולים להשתמש בבלוק
sourceSets
כדי לשנות את המקום שבו Gradle מחפש קבצים לכל רכיב של קבוצת מקורות.
הבלוק sourceSets
חייב להיות בתוך הבלוק android
. אין צורך להעביר את קובצי המקור. צריך רק לספק ל-Gradle את הנתיבים, ביחס לקובץ build.gradle.kts
ברמת המודול, שבהם Gradle יכול למצוא קבצים לכל רכיב של קבוצת מקור. כדי לדעת אילו רכיבים אפשר להגדיר ואם אפשר למפות אותם לכמה קבצים או ספריות, אפשר לעיין בהפניה ל-API של Android Gradle Plugin.
בדוגמת הקוד הבאה ממופים מקורות מהספרייה app/other/
לרכיבים מסוימים של קבוצת המקורות main
, ומשתנה ספריית השורש של קבוצת המקורות androidTest
:
Kotlin
android { ... // Encapsulates configurations for the main source set. sourceSets.getByName("main") { // Changes the directory for Java sources. The default directory is // 'src/main/java'. java.setSrcDirs(listOf("other/java")) // If you list multiple directories, Gradle uses all of them to collect // sources. Because Gradle gives these directories equal priority, if // you define the same resource in more than one directory, you receive an // error when merging resources. The default directory is 'src/main/res'. res.setSrcDirs(listOf("other/res1", "other/res2")) // Note: Avoid specifying a directory that is a parent to one // or more other directories you specify. For example, avoid the following: // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings'] // Specify either only the root 'other/res1' directory or only the // nested 'other/res1/layouts' and 'other/res1/strings' directories. // For each source set, you can specify only one Android manifest. // By default, Android Studio creates a manifest for your main source // set in the src/main/ directory. manifest.srcFile("other/AndroidManifest.xml") ... } // Create additional blocks to configure other source sets. sourceSets.getByName("androidTest") { // If all the files for a source set are located under a single root // directory, you can specify that directory using the setRoot property. // When gathering sources for the source set, Gradle looks only in locations // relative to the root directory you specify. For example, after applying the // configuration below for the androidTest source set, Gradle looks for Java // sources only in the src/tests/java/ directory. setRoot("src/tests") ... } } ...
מגניב
android { ... sourceSets { // Encapsulates configurations for the main source set. main { // Changes the directory for Java sources. The default directory is // 'src/main/java'. java.srcDirs = ['other/java'] // If you list multiple directories, Gradle uses all of them to collect // sources. Because Gradle gives these directories equal priority, if // you define the same resource in more than one directory, you receive an // error when merging resources. The default directory is 'src/main/res'. res.srcDirs = ['other/res1', 'other/res2'] // Note: Avoid specifying a directory that is a parent to one // or more other directories you specify. For example, avoid the following: // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings'] // Specify either only the root 'other/res1' directory or only the // nested 'other/res1/layouts' and 'other/res1/strings' directories. // For each source set, you can specify only one Android manifest. // By default, Android Studio creates a manifest for your main source // set in the src/main/ directory. manifest.srcFile 'other/AndroidManifest.xml' ... } // Create additional blocks to configure other source sets. androidTest { // If all the files for a source set are located under a single root // directory, you can specify that directory using the setRoot property. // When gathering sources for the source set, Gradle looks only in locations // relative to the root directory you specify. For example, after applying the // configuration below for the androidTest source set, Gradle looks for Java // sources only in the src/tests/java/ directory. setRoot 'src/tests' ... } } } ...
חשוב לדעת שספריית מקור יכולה להשתייך רק לקבוצת מקורות אחת. לדוגמה, אי אפשר לשתף את אותם מקורות בדיקה עם קבוצות המקורות test
ו-androidTest
. הסיבה לכך היא ש-Android Studio יוצר מודולים נפרדים של IntelliJ לכל קבוצת מקורות, ולא יכול לתמוך בשורשי תוכן כפולים בקבוצות מקורות שונות.
איך יוצרים קבוצות מקורות
אפשר להשתמש בספריות של ערכות מקור כדי לאחסן את הקוד והמשאבים שרוצים לארוז רק עם הגדרות מסוימות. לדוגמה, אם אתם יוצרים את וריאציית ה-build demoDebug, שהיא מכפלה קרטזית של טעם המוצר demo וסוג ה-build debug, מערכת Gradle בודקת את הספריות האלה ומקצה להן את העדיפות הבאה:
-
src/demoDebug/
(build variant source set) -
src/debug/
(מערך מקורות של סוג build) -
src/demo/
(מקור של גרסת מוצר) -
src/main/
(קבוצת מקורות ראשית)
כשיוצרים קבוצות מקור לשילובי טעמים של מוצרים, צריך לכלול את כל מאפייני הטעם. לדוגמה, קבוצת המקור של וריאנט הבנייה חייבת להיות שילוב של סוג הבנייה וכל המאפיינים של הטעם. אי אפשר למזג קוד ומשאבים שכוללים תיקיות שמכסות כמה מימדים של טעמים, אבל לא את כולם.
אם משלבים כמה טעמים של מוצר, העדיפות בין הטעמים של המוצר נקבעת לפי המימד של הטעם שאליו הם משתייכים. כשמציינים מאפייני טעם באמצעות המאפיין
android.flavorDimensions
, לטעמי המוצרים שמשויכים למאפיין הטעם הראשון שציינתם יש עדיפות גבוהה יותר מאשר לטעמים שמשויכים למאפיין הטעם השני, וכן הלאה. בנוסף,
קבוצות מקורות שיוצרים לשילובים של טעמי מוצרים מקבלות עדיפות גבוהה יותר מקבוצות מקורות ששייכות לטעם מוצר בודד.
סדר העדיפות קובע לאיזה מקור יש עדיפות גבוהה יותר כש-Gradle משלב קוד ומשאבים. ספריית קבוצת המקור demoDebug/
כנראה מכילה קבצים שספציפיים לגרסת הבנייה הזו, ולכן אם demoDebug/
כולל קובץ שמוגדר גם ב-debug/
, Gradle משתמש בקובץ בקבוצת המקור demoDebug/
. באופן דומה, Gradle נותן לקבצים ב-source sets של סוג ה-build ושל product flavor עדיפות גבוהה יותר מאשר לאותם קבצים ב-main/
.
מערכת Gradle מתחשבת בסדר העדיפות הזה כשהיא מחילה את כללי הבנייה הבאים:
- כל קוד המקור בספריות
kotlin/
אוjava/
עובר קומפילציה ביחד כדי ליצור פלט יחיד.הערה: עבור וריאציה נתונה של build, Gradle מחזיר שגיאת build אם הוא נתקל בשתי ספריות או יותר של קבוצות מקור שהוגדרה בהן אותה מחלקה של Kotlin או Java. לדוגמה, כשיוצרים אפליקציה לניפוי באגים, אי אפשר להגדיר גם את
src/debug/Utility.kt
וגם אתsrc/main/Utility.kt
, כי Gradle בודק את שתי הספריות האלה במהלך תהליך הבנייה ומציג שגיאה של 'כיתה כפולה'. אם רוצים גרסאות שונות שלUtility.kt
לסוגי build שונים, כל סוג build צריך להגדיר גרסה משלו של הקובץ ולא לכלול אותו ב-source set שלmain/
. - המניפסטים עוברים מיזוג למניפסט אחד. העדיפות ניתנת באותו סדר כמו הרשימה בדוגמה הקודמת. כלומר, הגדרות המניפסט של סוג build מבטלות את הגדרות המניפסט של טעם מוצר, וכן הלאה. מידע נוסף זמין במאמר בנושא מיזוג מניפסטים.
- הקבצים בספריות
values/
מוזגו יחד. אם לשני קבצים יש את אותו שם, למשל שני קבצים בשםstrings.xml
, העדיפות תינתן לפי הסדר שמופיע ברשימה בדוגמה הקודמת. כלומר, ערכים שמוגדרים בקובץ בערכת המקור של סוג ה-build מחליפים את הערכים שמוגדרים באותו קובץ בטעם מוצר, וכן הלאה. - המשאבים בספריות
res/
ו-asset/
נארזים יחד. אם יש משאבים עם אותו שם שמוגדרים בשתי קבוצות מקור או יותר, העדיפות ניתנת באותו סדר כמו ברשימה בדוגמה הקודמת. - Gradle נותן למשאבים ולמניפסטים שכלולים בהסתמכויות של מודול הספרייה את העדיפות הנמוכה ביותר כשמבצעים build לאפליקציה.
הצהרה על יחסי תלות
כדי להגדיר תלות עבור וריאנט בנייה ספציפי או קבוצת מקורות לבדיקה, מוסיפים את הקידומת של שם וריאנט הבנייה או קבוצת המקורות לבדיקה לפני מילת המפתח Implementation
, כמו בדוגמה הבאה:
Kotlin
dependencies { // Adds the local "mylibrary" module as a dependency to the "free" flavor. "freeImplementation"(project(":mylibrary")) // Adds a remote binary dependency only for local tests. testImplementation("junit:junit:4.12") // Adds a remote binary dependency only for the instrumented test APK. androidTestImplementation("com.android.support.test.espresso:espresso-core:3.6.1") }
מגניב
dependencies { // Adds the local "mylibrary" module as a dependency to the "free" flavor. freeImplementation project(":mylibrary") // Adds a remote binary dependency only for local tests. testImplementation 'junit:junit:4.12' // Adds a remote binary dependency only for the instrumented test APK. androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.6.1' }
מידע נוסף על הגדרת יחסי תלות זמין במאמר הוספת יחסי תלות ב-build.
שימוש בניהול תלות שמודע לגרסאות
הפלאגין Android Gradle מגרסה 3.0.0 ואילך כולל מנגנון חדש של יחסי תלות, שמתאים באופן אוטומטי בין וריאציות כשמשתמשים בספרייה. המשמעות היא שdebug
וריאציה
של אפליקציה צורכת באופן אוטומטי debug
וריאציה של ספרייה וכן הלאה. היא פועלת גם כשמשתמשים בטעמים: וריאציה של אפליקציה freeDebug
תשתמש בווריאציה של ספרייה freeDebug
.
כדי שהתוסף יתאים בין וריאציות בצורה מדויקת, צריך לספק חלופות תואמות כמו שמתואר בקטע הבא, למקרים שבהם לא ניתן לבצע התאמה ישירה.
לדוגמה, נניח שהאפליקציה שלכם מגדירה סוג build בשם staging, אבל אחד מיחסי התלות של הספריות שלה לא מגדיר אותו. כשהתוסף מנסה ליצור את גרסת ה-staging של האפליקציה, הוא לא יודע באיזו גרסה של הספרייה להשתמש, ותופיע הודעת שגיאה שדומה להודעה הבאה:
Error:Failed to resolve: Could not resolve project :mylibrary. Required by: project :app
פתרון שגיאות שקשורות להתאמת וריאנטים
התוסף כולל רכיבי DSL שעוזרים לכם לשלוט באופן שבו Gradle פותר מצבים שבהם לא ניתן להתאים ישירות בין אפליקציה לבין תלות.
בהמשך מופיעה רשימה של בעיות שקשורות להתאמת תלות עם התחשבות בווריאציות, והסבר איך לפתור אותן באמצעות מאפייני DSL:האפליקציה שלך כוללת סוג build שאין לו תלות בספרייה.
לדוגמה, האפליקציה כוללת סוג build מסוג staging, אבל תלות כוללת רק סוגי build מסוג debug ו-release.
שימו לב: אין בעיה אם תלות בספרייה כוללת סוג build שהאפליקציה שלכם לא כוללת. הסיבה לכך היא שהתוסף אף פעם לא מבקש את סוג הבנייה הזה מהתלות.
משתמשים ב-
matchingFallbacks
כדי לציין התאמות חלופיות לסוג build נתון, כמו בדוגמה הבאה:Kotlin
// In the app's build.gradle.kts file. android { buildTypes { getByName("debug") {} getByName("release") {} create("staging") { // Specifies a sorted list of fallback build types that the // plugin can try to use when a dependency does not include a // "staging" build type. You may specify as many fallbacks as you // like, and the plugin selects the first build type that's // available in the dependency. matchingFallbacks += listOf("debug", "qa", "release") } } }
מגניב
// In the app's build.gradle file. android { buildTypes { debug {} release {} staging { // Specifies a sorted list of fallback build types that the // plugin can try to use when a dependency does not include a // "staging" build type. You may specify as many fallbacks as you // like, and the plugin selects the first build type that's // available in the dependency. matchingFallbacks = ['debug', 'qa', 'release'] } } }
במקרה של מאפיין טעם נתון שקיים גם באפליקציה וגם בתלות בספרייה שלה, האפליקציה כוללת טעמים שלא קיימים בספרייה.
לדוגמה, גם האפליקציה וגם יחסי התלות שלה בספריות כוללים מאפיין של טעם בשם tier (רמה). עם זאת, המאפיין 'רמה' באפליקציה כולל גרסאות 'חינמיות' ו'בתשלום', אבל יחסי תלות כוללים רק גרסאות 'הדגמה' ו'בתשלום' לאותו מאפיין.
שימו לב: אם יש מאפיין טעם מסוים שקיים גם באפליקציה וגם ביחסי התלות של הספריות שלה, לא תהיה בעיה אם ספרייה כוללת מאפיין טעם שהאפליקציה לא כוללת. הסיבה לכך היא שהתוסף אף פעם לא מבקש את הטעם הזה מהתלות.
משתמשים ב-
matchingFallbacks
כדי לציין התאמות חלופיות לגרסת המוצר 'החינמית' של האפליקציה, כמו שמוצג כאן:Kotlin
// In the app's build.gradle.kts file. android { defaultConfig{ // Don't configure matchingFallbacks in the defaultConfig block. // Instead, specify fallbacks for a given product flavor in the // productFlavors block, as shown below. } flavorDimensions += "tier" productFlavors { create("paid") { dimension = "tier" // Because the dependency already includes a "paid" flavor in its // "tier" dimension, you don't need to provide a list of fallbacks // for the "paid" flavor. } create("free") { dimension = "tier" // Specifies a sorted list of fallback flavors that the plugin // can try to use when a dependency's matching dimension does // not include a "free" flavor. Specify as many // fallbacks as you like; the plugin selects the first flavor // that's available in the dependency's "tier" dimension. matchingFallbacks += listOf("demo", "trial") } } }
מגניב
// In the app's build.gradle file. android { defaultConfig{ // Don't configure matchingFallbacks in the defaultConfig block. // Instead, specify fallbacks for a given product flavor in the // productFlavors block, as shown below. } flavorDimensions 'tier' productFlavors { paid { dimension 'tier' // Because the dependency already includes a "paid" flavor in its // "tier" dimension, you don't need to provide a list of fallbacks // for the "paid" flavor. } free { dimension 'tier' // Specifies a sorted list of fallback flavors that the plugin // can try to use when a dependency's matching dimension does // not include a "free" flavor. Specify as many // fallbacks as you like; the plugin selects the first flavor // that's available in the dependency's "tier" dimension. matchingFallbacks = ['demo', 'trial'] } } }
תלות בספרייה כוללת מאפיין של טעם שהאפליקציה לא כוללת.
לדוגמה, יחסי תלות של ספריות כוללים טעמים למאפיין minApi, אבל האפליקציה שלכם כוללת טעמים רק למאפיין tier. כשרוצים ליצור את הגרסה freeDebug של האפליקציה, הפלאגין לא יודע אם להשתמש בגרסה minApi23Debug או בגרסה minApi18Debug של התלות.
שימו לב: אין בעיה אם האפליקציה כוללת מאפיין flavor שספרייה עם יחסי תלות לא כוללת. הסיבה לכך היא שהתוסף מתאים טעמים רק למאפיינים שקיימים בתלות. לדוגמה, אם יחסי תלות לא כוללים מימד של ABI, הגרסה freeX86Debug של האפליקציה תשתמש בגרסה freeDebug של יחסי התלות.
משתמשים ב-
missingDimensionStrategy
בבלוקdefaultConfig
כדי לציין את טעם ברירת המחדל שהתוסף יבחר מכל מאפיין חסר, כמו שמוצג בדוגמה הבאה. אפשר גם לבטל את הבחירות שלכם בבלוקproductFlavors
כך שכל טעם יוכל לציין אסטרטגיית התאמה שונה למאפיין חסר.Kotlin
// In the app's build.gradle.kts file. android { defaultConfig{ // Specifies a sorted list of flavors that the plugin can try to use from // a given dimension. This tells the plugin to select the "minApi18" flavor // when encountering a dependency that includes a "minApi" dimension. // You can include additional flavor names to provide a // sorted list of fallbacks for the dimension. missingDimensionStrategy("minApi", "minApi18", "minApi23") // Specify a missingDimensionStrategy property for each // dimension that exists in a local dependency but not in your app. missingDimensionStrategy("abi", "x86", "arm64") } flavorDimensions += "tier" productFlavors { create("free") { dimension = "tier" // You can override the default selection at the product flavor // level by configuring another missingDimensionStrategy property // for the "minApi" dimension. missingDimensionStrategy("minApi", "minApi23", "minApi18") } create("paid") {} } }
מגניב
// In the app's build.gradle file. android { defaultConfig{ // Specifies a sorted list of flavors that the plugin can try to use from // a given dimension. This tells the plugin to select the "minApi18" flavor // when encountering a dependency that includes a "minApi" dimension. // You can include additional flavor names to provide a // sorted list of fallbacks for the dimension. missingDimensionStrategy 'minApi', 'minApi18', 'minApi23' // Specify a missingDimensionStrategy property for each // dimension that exists in a local dependency but not in your app. missingDimensionStrategy 'abi', 'x86', 'arm64' } flavorDimensions 'tier' productFlavors { free { dimension 'tier' // You can override the default selection at the product flavor // level by configuring another missingDimensionStrategy property // for the 'minApi' dimension. missingDimensionStrategy 'minApi', 'minApi23', 'minApi18' } paid {} } }
מידע נוסף זמין במאמרים בנושא matchingFallbacks
וmissingDimensionStrategy
בתיעוד של Android Gradle plugin DSL.
הגדרת הגדרות החתימה
Gradle לא חותם על ה-APK או על ה-AAB של גרסת ההפצה, אלא אם מגדירים במפורש הגדרת חתימה לגרסה הזו. אם עדיין אין לכם מפתח חתימה, צריך ליצור מפתח העלאה ומאגר מפתחות באמצעות Android Studio.
כדי להגדיר באופן ידני את הגדרות החתימה לסוג build של גרסת ההפצה באמצעות הגדרות build של Gradle:
- יוצרים מאגר מפתחות. מאגר מפתחות הוא קובץ בינארי שמכיל קבוצה של מפתחות פרטיים. חשוב לשמור את מאגר המפתחות במקום בטוח ומאובטח.
- יוצרים מפתח פרטי. מפתח פרטי משמש לחתימה על האפליקציה לצורך הפצה, והוא אף פעם לא נכלל באפליקציה או נחשף לצדדים שלישיים לא מורשים.
-
מוסיפים את הגדרות החתימה לקובץ
build.gradle.kts
ברמת המודול:Kotlin
... android { ... defaultConfig {...} signingConfigs { create("release") { storeFile = file("myreleasekey.keystore") storePassword = "password" keyAlias = "MyReleaseKey" keyPassword = "password" } } buildTypes { getByName("release") { ... signingConfig = signingConfigs.getByName("release") } } }
מגניב
... android { ... defaultConfig {...} signingConfigs { release { storeFile file("myreleasekey.keystore") storePassword "password" keyAlias "MyReleaseKey" keyPassword "password" } } buildTypes { release { ... signingConfig signingConfigs.release } } }
הערה: לא מומלץ לכלול את הסיסמאות של מפתח ההפצה ומאגר המפתחות בקובץ ה-build. במקום זאת, מגדירים את קובץ ה-build כך שיקבל את הסיסמאות האלה ממשתני סביבה, או מגדירים את תהליך ה-build כך שהוא יבקש את הסיסמאות האלה.
כדי לקבל את הסיסמאות האלה ממשתני סביבה:
Kotlin
storePassword = System.getenv("KSTOREPWD") keyPassword = System.getenv("KEYPWD")
מגניב
storePassword System.getenv("KSTOREPWD") keyPassword System.getenv("KEYPWD")
לחלופין, אפשר לטעון את מאגר המפתחות מקובץ מאפיינים מקומי. מטעמי אבטחה, אל תוסיפו את הקובץ הזה לבקרת המקור. במקום זאת, מגדירים אותו באופן מקומי לכל מפתח. מידע נוסף זמין במאמר בנושא הסרת פרטי חתימה מקובצי ה-build.
אחרי שתסיימו את התהליך הזה, תוכלו להפיץ את האפליקציה ולפרסם אותה ב-Google Play.
אזהרה: חשוב לשמור את מאגר המפתחות ואת המפתח הפרטי במקום בטוח ומאובטח, ולוודא שיש לכם גיבויים מאובטחים שלהם. אם אתם משתמשים בחתימת אפליקציות ב-Play ואבד לכם מפתח ההעלאה, אתם יכולים לבקש איפוס באמצעות Play Console. אם אתם מפרסמים אפליקציה ללא חתימת אפליקציה ב-Play (לאפליקציות שנוצרו לפני אוגוסט 2021) ואתם מאבדים את חתימת האפליקציה, לא תוכלו לפרסם עדכונים לאפליקציה, כי תמיד צריך לחתום על כל הגרסאות של האפליקציה עם אותה חתימה.
חתימה על אפליקציות Wear OS
כשמפרסמים אפליקציות ל-Wear OS, חבילת ה-APK של השעון וחבילת ה-APK של הטלפון (אם יש כזו) צריכות להיות חתומות באותו מפתח. מידע נוסף על אריזה וחתימה של אפליקציות ל-Wear OS זמין במאמר בנושא אריזה והפצה של אפליקציות ל-Wear.