בדף הזה מוסבר איך להגדיר וריאנטים של 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 מזהה שגיאות בהגדרות, יוצג החלון Messages כדי לתאר את הבעיה.
כדי לקבל מידע נוסף על כל המאפיינים שאפשר להגדיר בסוגי ה-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" } } }
מגניב
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>
. לדוגמה, אם יצרתם את טעמי המוצר מסוג 'הדגמה' ו'מלא' והשארתם את סוגי ה-build שמוגדרים כברירת מחדל
'ניפוי באגים' ו'הפצה', מערכת 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 כטעם מוצר נפרד. לכל טעם בבלוק productFlavors
, אפשר להגדיר מחדש את הנכס applicationId
או במקום זאת להוסיף פלח למזהה האפליקציה שמוגדר כברירת מחדל באמצעות applicationIdSuffix
, כפי שמוצג כאן:
Kotlin
android { defaultConfig { applicationId = "com.example.myapp" } productFlavors { create("free") { applicationIdSuffix = ".free" } create("pro") { applicationIdSuffix = ".pro" } } }
Groovy
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" ... } } } ...
מגניב
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 } } } ...
מגניב
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 ליצור את רשימת המשימות במהלך הסנכרון. לשם כך, בצע את הצעדים הבאים:
- לוחצים על 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/
לסוג ה-build 'ניפוי באגים':
- פותחים את החלונית 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 או משאירים את שם ברירת המחדל.
- בתפריט שלצד Target Source Set, בוחרים באפשרות debug.
- לוחצים על סיום.
מכיוון שציינתם את סוג ה-build 'debug' כקבוצת המקור של היעד, Android Studio יוצרת את הספריות הנדרשות באופן אוטומטי כשהיא יוצרת את קובץ ה-XML. מבנה הספריות שנוצר נראה כמו באיור 1.
קבוצות מקורות פעילות מסומנות בסמל ירוק כדי לציין שהן פעילות. קבוצת המקור debug
מסתיימת ב-[main]
כדי להראות שהיא תמוזג עם קבוצת המקור main
.
באותו תהליך, אפשר גם ליצור ספריות של קבוצת מקור לטעמים של מוצרים, כמו src/demo/
, ולבנות וריאציות, כמו 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 ל'ניפוי באגים', Gradle תבדוק את הספריות האלה ותקצה להן את העדיפות הבאה:
-
src/demoDebug/
(קבוצת מקורות של וריאנטים של build) -
src/debug/
(קבוצת מקור מסוג build) -
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 ספציפית או לקבוצת מקורות לבדיקה, מוסיפים את השם של גרסת ה-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'] } } }
לגבי מימד טעם נתון שקיים גם באפליקציה וגם בתלויות שלה בספרייה, האפליקציה שלך כוללת טעמים שלא קיימים בספרייה.
לדוגמה, גם האפליקציה וגם יחסי התלות של הספרייה כוללים מאפיין טעם מסוג 'רמה'. עם זאת, המאפיין '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', אבל האפליקציה שלכם כוללת טעמים רק למאפיין 'tier'. כשרוצים ליצור את הגרסה '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") } } }
מגניב
... 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.