בדף הזה נסביר איך להגדיר וריאנטים של גרסאות build כדי ליצור גרסאות שונות של האפליקציה מפרויקט אחד, ואיך לנהל בצורה נכונה את יחסי התלות וההגדרות של החתימה.
כל גרסת build מייצגת גרסה אחרת של האפליקציה שאפשר ליצור. לדוגמה, כדאי ליצור גרסה אחת של האפליקציה בחינם עם קבוצה מוגבלת של תוכן, וגרסה אחרת בתשלום שכוללת גרסאות נוספות. אפשר גם ליצור גרסאות שונות של האפליקציה שמותאמות למכשירים שונים, על סמך רמת ה-API או וריאציות אחרות של המכשיר.
וריאנטים של build הם תוצאה של Gradle שמשתמש בקבוצה ספציפית של כללים כדי לשלב הגדרות, קוד ומשאבים שמוגדרים בסוגים של ה-build ובטעמי המוצר. אתם לא מגדירים ישירות את הווריאציות של הגרסאות המהדורות של ה-build, אבל אתם כן מגדירים את סוגי ה-build ואת טעמי המוצר שמרכיבים אותן.
לדוגמה, סוג המוצר 'דמו' עשוי לציין מאפיינים מסוימים ודרישות למכשירים, כמו קוד מקור מותאם אישית, משאבים ורמות API מינימלי, בעוד שסוג ה-build 'ניפוי באגים' מחיל הגדרות שונות של build ואריזות, כמו אפשרויות לניפוי באגים ומפתחות חתימה. גרסת ה-build המשולבת של השניים היא הגרסה 'demoDebug' של האפליקציה, והיא כוללת שילוב של ההגדרות והמשאבים הכלולים בטעמה של המוצר 'demo', סוג ה-build '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" } } }
Groovy
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 תזהה שגיאות בהגדרות, יופיע החלון הודעות עם תיאור הבעיה.
מידע נוסף על כל המאפיינים שאפשר להגדיר באמצעות סוגי גרסאות build זמין במסמך העזרה של
BuildType
.
הגדרת גרסאות של מוצרים
יצירת טעמים למוצרים דומה ליצירת סוגי גרסאות build. מוסיפים את טעמי המוצרים לבלוק productFlavors
בתצורת ה-build וכוללים את ההגדרות הרצויות.
גרסאות המוצר תומכות באותם מאפיינים כמו defaultConfig
, כי defaultConfig
שייך למעשה לסוג
ProductFlavor
. כלומר, אפשר לספק את ההגדרות הבסיסיות לכל הגרסאות בבלוק defaultConfig
, וכל גרסה יכולה לשנות כל אחד מערכי ברירת המחדל האלה, כמו applicationId
. למידע נוסף על מזהה האפליקציה, ראו הגדרת מזהה האפליקציה.
הערה: עדיין צריך לציין שם חבילה באמצעות המאפיין package
בקובץ המניפסט main/
. צריך להשתמש בשם החבילה הזה גם בקוד המקור כדי להפנות לכיתה R
או כדי לפתור כל רישום פעילות או שירות יחסי. כך תוכלו להשתמש ב-applicationId
כדי לתת לכל גרסת מוצר מזהה ייחודי לחבילה ולהפצה, בלי לשנות את קוד המקור.
כל הטעמים חייבים להשתייך למאפיין טעם שצוין, שהוא קבוצה של טעמים. חובה להקצות את כל המאפיינים למאפיין של טעם. אחרת, תוצג שגיאת build הבאה.
Error: All flavors must now belong to a named flavor dimension. The flavor 'flavor_name' is not assigned to a flavor dimension.
אם מודול נתון מציין רק מימד טעם אחד, הפלאגין של Android Gradle מקצה אוטומטית את כל הטעמים של המודול הזה.
בדוגמת הקוד הבאה יוצרים מאפיין 'גרסה' ומוסיפים את הגרסאות 'דמו' ו'מלא'. לכל אחד מהטעמים האלה יש
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" } } }
Groovy
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
שונה לכל גרסה.
אחרי שיוצרים ומגדירים את גרסאות המוצר, לוחצים על Sync Now (סנכרון עכשיו) בסרגל ההתראות. בסיום הסנכרון, Gradle יוצר באופן אוטומטי וריאנטים של גרסאות build על סמך סוגי ה-build וסוג המוצר, ומעניק להם שמות בהתאם ל-<product-flavor><Build-Type>
. לדוגמה, אם יצרתם טעמי מוצר בשם 'demo' ו-'full' ושמרתם את סוגי ה-build שמוגדרים כברירת מחדל, 'debug' ו-'release', Gradle ייצור את וריאנטים הבאים של ה-build:
-
demoDebug
-
demoRelease
-
fullDebug
-
fullRelease
כדי לבחור איזו גרסת build ליצור ולהריץ, עוברים אל Build > Select Build Variant ובוחרים גרסת build מהתפריט. כדי להתחיל להתאים אישית כל וריאנט של build באמצעות תכונות ומשאבים משלו, צריך ליצור ולנהל קבוצות מקור, כפי שמתואר בדף הזה.
שינוי מזהה האפליקציה לווריאציות של גרסת build
כשאתם יוצרים קובץ APK או AAB לאפליקציה, כלי ה-build מתייגים את האפליקציה באמצעות מזהה האפליקציה שמוגדר בבלוק defaultConfig
בקובץ build.gradle.kts
, כפי שמתואר בדוגמה הבאה. עם זאת, אם אתם רוצים ליצור גרסאות שונות של האפליקציה שיופיעו ככרטיסי מוצר נפרדים בחנות Google Play, כמו גרסה 'חינמית' וגרסה 'Pro', תצטרכו ליצור גרסאות build נפרדות עם מזהה אפליקציה שונה לכל אחת מהן.
במקרה כזה, צריך להגדיר כל וריאנט של 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" } } }
כך, מזהה האפליקציה של גרסת המוצר 'חינם' הוא com.example.myapp.free.
אפשר גם להשתמש ב-applicationIdSuffix
כדי לצרף פלח על סמך סוג ה-build, כפי שמוצג כאן:
Kotlin
android { ... buildTypes { getByName("debug") { applicationIdSuffix = ".debug" } } }
Groovy
android { ... buildTypes { debug { applicationIdSuffix ".debug" } } }
מכיוון ש-Gradle מחיל את ההגדרה של סוג ה-build אחרי טעמי המוצר, מזהה האפליקציה של וריאנט ה-build 'free debug' הוא 'com.example.myapp.free.debug'. האפשרות הזו שימושית כשרוצים שיהיו באותו מכשיר גם את גרסת ה-build של ניפוי הבאגים וגם את הגרסה, כי לאף אחת מהאפליקציות לא יכול להיות אותו מזהה אפליקציה.
אם יש לכם אפליקציה מדור קודם (שנוצרה לפני אוגוסט 2021) שאתם מפיצים באמצעות חבילות APK ב-Google Play, ואתם רוצים להשתמש באותה כרטיס אפליקציה כדי להפיץ כמה חבילות APK, שכל אחת מהן מטרגטת הגדרת מכשיר שונה, כמו רמת ה-API, עליכם להשתמש באותו מזהה אפליקציה לכל גרסת build, אבל לתת לכל APK ערךversionCode
שונה. למידע נוסף, קראו את המאמר תמיכה בכמה קובצי APK. פרסום באמצעות AAB לא מושפע, כי הוא מתבסס על ארטיפקט אחד שמשתמש בקוד גרסה ובמזהה אפליקציה יחידים כברירת מחדל.
טיפ: אם אתם צריכים להפנות למזהה האפליקציה בקובץ המניפסט, תוכלו להשתמש ב-placeholder ${applicationId}
בכל מאפיין מניפסט. במהלך ה-build, 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" ... } } } ...
Groovy
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.
לצורך הדגמה, Gradle יוצרת בסך הכול 12 וריאנטים של build עם סכימת השמות הבאה:
- וריאנט build:
[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
- קובץ ה-APK התואם:
app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
- לדוגמה,
- וריאנט build:
minApi24DemoDebug
- חבילת ה-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 } } } ...
Groovy
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 לא מופיעים יותר בתפריט כשלוחצים על Build > Select Build Variant בסרגל התפריטים או על Build Variants בסרגל חלון הכלים.
יצירת קבוצות מקורות
כברירת מחדל, Android Studio יוצר את קבוצת המקור main/
ואת הספריות לכל מה שרוצים לשתף בין כל הווריאנטים של ה-build. עם זאת, אפשר ליצור קבוצות מקור חדשות כדי לקבוע בדיוק אילו קבצים Gradle ידרבן ויארוז עבור סוגים ספציפיים של גרסאות build, טעמי מוצרים, שילובים של טעמי מוצרים (כשמשתמשים במאפייני טעמים) וריאנטים של גרסאות build.
לדוגמה, אפשר להגדיר פונקציונליות בסיסית בקבוצת המקור main/
ולהשתמש בקבוצות מקור של טעמי המוצר כדי לשנות את המיתוג של האפליקציה ללקוחות שונים, או לכלול הרשאות מיוחדות ופונקציונליות של רישום ביומן רק לגרסאות build שמשתמשות בסוג build לניפוי באגים.
מערכת Gradle מצפה שהקבצים והספריות של קבוצת המקור יהיו מאורגנים באופן מסוים, בדומה לקבוצת המקור main/
. לדוגמה, קובצי הכיתה של Kotlin או Java שספציפיים לסוג ה-build 'debug' אמורים להימצא בתיקיות src/debug/kotlin/
או src/debug/java/
.
פלאגין Android Gradle מספק משימה שימושית ב-Gradle שמראה איך לארגן את הקבצים לכל אחד מסוגי ה-build, טעמי המוצרים וריאנטים של ה-build. לדוגמה, הדוגמה הבאה מפלט המשימה מתארת את המיקום שבו 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 ליצור את רשימת המשימות במהלך הסנכרון. לשם כך, בצע את הצעדים הבאים:
- לוחצים על קובץ > הגדרות > ניסיוני (Android Studio > הגדרות > ניסיוני ב-macOS).
- מבטלים את הבחירה באפשרות לא ליצור את רשימת המשימות של Gradle במהלך הסנכרון של Gradle.
- אחרי ש-Gradle יבצע את המשימה, החלון Run ייפתח כדי להציג את הפלט.
הערה: בפלט של המשימה מוסבר גם איך לארגן קבוצות מקורות של קבצים שבהם רוצים להשתמש כדי להריץ בדיקות לאפליקציה, כמו קבוצות מקורות לבדיקה של test/
ו-androidTest/
.
כשיוצרים גרסת build חדשה, מערכת Android Studio לא יוצרת בשבילכם את הספריות של קבוצת המקורות, אבל היא מספקת כמה אפשרויות שיעזרו לכם. לדוגמה, כדי ליצור רק את התיקייה java/
לסוג ה-build 'debug':
- פותחים את החלונית Project ובוחרים בתצוגה Project מהתפריט שבחלק העליון של החלונית.
- נווט אל
MyProject/app/src/
. - לוחצים לחיצה ימנית על הספרייה
src
ובוחרים באפשרות New (חדש) > Directory. - בתפריט בקטע Gradle Source Sets, בוחרים באפשרות full/java.
- מקישים על Enter.
Android Studio יוצרת ספריית קבוצת מקורות לסוג ה-build של ניפוי הבאגים, ואז יוצרת את הספרייה java/
בתוכה. לחלופין, אפשר להשתמש ב-Android Studio כדי ליצור את הספריות כשאתם מוסיפים קובץ חדש לפרויקט עבור גרסת build ספציפית.
לדוגמה, כדי ליצור קובץ XML של ערכים לסוג ה-build 'debug':
- בחלונית Project, לוחצים לחיצה ימנית על הספרייה
src
ובוחרים New > XML > Values XML File (קובץ XML של ערכים). - מזינים את השם של קובץ ה-XML או משאירים את שם ברירת המחדל.
- בתפריט שלצד Target Source Set, בוחרים באפשרות debug.
- לוחצים על סיום.
מכיוון שציינתם את סוג ה-build 'debug' כקבוצת המקור של היעד, Android Studio יוצרת את הספריות הנדרשות באופן אוטומטי כשהיא יוצרת את קובץ ה-XML. מבנה הספריות שנוצר נראה כמו באיור 1.
קבוצות מקורות פעילות מסומנות בסמל ירוק כדי לציין שהן פעילות. קבוצת המקור debug
מסתיימת ב-[main]
כדי להראות שהיא תמוזג עם קבוצת המקור main
.
באמצעות אותה פרוצדורה, אפשר גם ליצור תיקיות של קבוצות מקורות למאפייני מוצרים, כמו src/demo/
, ולגרסאות build, כמו src/demoDebug/
. בנוסף, אפשר ליצור קבוצות של מקורות לבדיקה שמטרגטות וריאנטים ספציפיים של גרסאות build, כמו src/androidTestDemoDebug/
. מידע נוסף זמין במאמר בדיקת קבוצות מקור.
שינוי ההגדרות של קבוצת המקור שמוגדרת כברירת מחדל
אם יש לכם מקורות שלא מאורגנים במבנה ברירת המחדל של קובצי קבוצת המקור ש-Gradle מצפה לו, כפי שמתואר בקטע הקודם בנושא יצירת קבוצות מקור, תוכלו להשתמש בבלוק
sourceSets
כדי לשנות את המיקום שבו Gradle מחפש קבצים לכל רכיב של קבוצת מקור.
הבלוק sourceSets
חייב להיות בבלוק android
. אין צורך להעביר את קובצי המקור למיקום אחר. צריך רק לספק ל-Gradle את הנתיבים היחסיים לקובץ build.gradle.kts
ברמת המודול, שבהם Gradle יכול למצוא קבצים לכל רכיב של קבוצת המקור. במאמר Android Gradle plugin API reference מוסבר אילו רכיבים אפשר להגדיר ואם אפשר למפות אותם לכמה קבצים או תיקיות.
דוגמת הקוד הבאה ממפה מקורות מהספרייה 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") ... } } ...
Groovy
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', שהיא המוצר המשולב של סוג ה-build 'debug' וסוג המוצר 'demo', Gradle בודק את הספריות האלה ומקצה להן את העדיפות הבאה:
-
src/demoDebug/
(קבוצת מקורות של וריאנטים של build) -
src/debug/
(build type source set) -
src/demo/
(קבוצת מקורות של גרסת המוצר) -
src/main/
(קבוצת המקור הראשית)
קבוצות מקורות שנוצרות לשילובים של טעמי מוצרים חייבות לכלול את כל מאפייני הטעם. לדוגמה, קבוצת המקור של הווריאנטים של build חייבת לכלול שילוב של סוג ה-build וכל מאפייני הגרסה. אין תמיכה במיזוג קוד ומשאבים שכוללים תיקיות שמכסות כמה מאפייני, אבל לא את כולם.
אם משלבים כמה טעמים של מוצרים, העדיפות בין טעמי המוצרים נקבעת לפי המאפיין של הטעם שאליו הם שייכים. כשמציינים מאפייני טעמים באמצעות המאפיין
android.flavorDimensions
, טעמי המוצרים ששייכים למאפיין הטעם הראשון שציינתם בעדיפות גבוהה יותר מאלה ששייכים למאפיין הטעם השני, וכן הלאה. בנוסף, למערכי מקורות שאתם יוצרים לשילובים של טעמי מוצרים יש עדיפות גבוהה יותר מאשר למערכי מקורות ששייכים לטעם מוצר ספציפי.
סדר העדיפויות קובע לאילו קבוצות מקורות יש עדיפות גבוהה יותר כש-Gradle משלבת קוד ומשאבים. סביר להניח שספריית demoDebug/
מכילה קובצי מקור ספציפיים לגרסה הזו של ה-build, ולכן אם demoDebug/
כולל קובץ שמוגדר גם ב-debug/
, Gradle משתמש בקובץ בקבוצת המקור demoDebug/
. באופן דומה, ב-Gradle, הקבצים בסוג ה-build ובגרסת המקור בגרסת המוצר
מקבלים עדיפות גבוהה יותר מאשר אותם קבצים ב-main/
.
Gradle מתייחס לסדר העדיפות הזה כשמחילים את כללי ה-build הבאים:
- כל קוד המקור בתיקיות
kotlin/
אוjava/
עובר הידור יחד כדי ליצור פלט יחיד.הערה: לגבי וריאנט build נתון, Gradle יוצר שגיאת build אם הוא נתקל בשתי ספריות או יותר של קבוצות מקורות שהוגדרה בהן אותה כיתה של Kotlin או Java. לדוגמה, כשמפתחים אפליקציה לניפוי באגים, אי אפשר להגדיר גם את
src/debug/Utility.kt
וגם אתsrc/main/Utility.kt
, כי Gradle בודק את שתי הספריות האלה במהלך תהליך ה-build ומציג את השגיאה 'duplicate class'. אם רוצים גרסאות שונות שלUtility.kt
לסוגים שונים של גרסאות build, צריך להגדיר לכל סוג build גרסה משלו של הקובץ ולא לכלול אותו בקבוצת המקורותmain/
. - המניפסטים מוזגו למניפסט אחד. העדיפות ניתנת לפי הסדר של הרשימה בדוגמה הקודמת. כלומר, הגדרות המניפסט של סוג build מבטלות את הגדרות המניפסט של גרסת המוצר, וכן הלאה. למידע נוסף, קראו את המאמר בנושא מיזוג מניפסט.
- הקבצים בספריות
values/
ממוזגים יחד. אם לשני קבצים יש שם זהה, למשל שני קבצים מסוגstrings.xml
, העדיפות ניתנת לפי הסדר שמופיע ברשימה בדוגמה הקודמת. כלומר, ערכים שהוגדרו בקובץ בקבוצת המקורות של סוג ה-build מבטלים את הערכים שהוגדרו באותו קובץ ב-flavor של המוצר, וכן הלאה. - המשאבים בספריות
res/
ו-asset/
נארזים יחד. אם יש משאבים עם אותו שם שמוגדרים בשתי קבוצות מקורות או יותר, העדיפות תינתן באותו סדר כמו הרשימה בדוגמה הקודמת. - כשמפתחים את האפליקציה, המשאבים והמניפסטים ב-Gradle מקבלים את העדיפות הכי נמוכה ביחסי תלות של מודולי ספריות.
הצהרת יחסי תלות
כדי להגדיר תלות בווריאנט build ספציפי או בקבוצת מקורות בדיקה ספציפית, כדאי להוסיף קידומת לשם של הווריאנט של ה-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") }
Groovy
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
פתרון שגיאות build שקשורות להתאמת וריאנטים
הפלאגין כולל אלמנטים של DSL שיעזרו לכם לקבוע איך Gradle יטפל במצבים שבהם אי אפשר להתאים וריאנט ישירות בין אפליקציה לבין יחסי תלות.
בהמשך מופיעה רשימה של בעיות שקשורות להתאמה של תלות מבוססת-וריאנטים, והאופן שבו אפשר לפתור אותן באמצעות מאפייני DSL:האפליקציה שלך כוללת סוג build שלא קשור התלות של ספרייה.
לדוגמה, האפליקציה כוללת סוג build מסוג 'staging', אבל יחסי התלות כוללים רק סוגי build מסוג 'debug' ו-'release'.
חשוב לזכור שאין בעיה אם יחסי התלות בספרייה כוללים סוג build שאינו נכלל באפליקציה. הסיבה לכך היא שהפלאגין אף פעם לא מבקש סוג 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") } } }
Groovy
// 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'] } } }
במימד מסוים של טעמים שקיים גם באפליקציה וגם בספרייה שאליה היא תלויה, האפליקציה כוללת טעמים שלא קיימים בספרייה.
לדוגמה, גם האפליקציה וגם יחסי התלות שלה בספריות כוללים מאפיין 'סוג' (flavor) בשם 'tier'. עם זאת, המאפיין 'tier' באפליקציה כולל את הגרסאות 'free' ו-'paid', אבל יחסי התלות כוללים רק את הגרסאות 'demo' ו-'paid' של אותו מאפיין.
חשוב לזכור: אם יש מאפיין טעימה מסוים שקיים גם באפליקציה וגם בספריות שאליה היא תלויה, אין בעיה אם הספרייה כוללת טעימה של מוצר שלא נכללת באפליקציה. הסיבה לכך היא שהתוסף אף פעם לא מבקש את הטעם הזה מהתלות.
משתמשים ב-
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") } } }
Groovy
// 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'] } } }
יחסי תלות בספרייה כוללים מאפיין של גרסת build שאינו קיים באפליקציה.
לדוגמה, תלות של ספרייה כוללת טעמים של המאפיין minApi, אבל האפליקציה כוללת טעמים רק למאפיין 'רמה'. כשרוצים ליצור את הגרסה 'freeDebug' של האפליקציה, הפלאגין לא יודע אם להשתמש בגרסת התלות 'minApi23Debug' או בגרסת התלות 'minApi18Debug'.
חשוב לזכור שאין בעיה אם האפליקציה כוללת מאפיין טעימה שיחסי התלות בספרייה לא כוללים. הסיבה לכך היא שהתוסף מתאים בין טעמים רק של המאפיינים שקיימים ביחס התלות. לדוגמה, אם התלות לא כוללת מאפיין של ממשקי 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") {} } }
Groovy
// 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") } } }
Groovy
... 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")
Groovy
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.