בדף הזה נסביר איך להגדיר וריאנטים של גרסאות 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 וטעמי המוצרים, ונותנת להן שמות בהתאם ל-<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 כטעם מוצר נפרד. לכל גרסת 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'. האפשרות הזו שימושית אם רוצים להריץ גם את הגרסה לניפוי באגים וגם את הגרסה המהדורה באותו מכשיר, כי לא יכולות להיות שתי אפליקציות עם אותו מזהה אפליקציה.
אם יש לכם אפליקציה מדור קודם (שנוצרה לפני אוגוסט 2021) שאתם מפיצים באמצעות חבילות APK ב-Google Play, ואתם רוצים להשתמש באותה כרטיס אפליקציה כדי להפיץ כמה חבילות APK, שכל אחת מהן מטרגטת הגדרת מכשיר שונה, כמו רמת ה-API, עליכם להשתמש באותו מזהה אפליקציה לכל גרסת build, אבל לתת לכל APK ערךversionCode
שונה. למידע נוסף, קראו את המאמר תמיכה בכמה קובצי APK. פרסום באמצעות AAB לא מושפע, כי הוא מתבסס על ארטיפקט אחד שמשתמש בקוד גרסה ובמזהה אפליקציה יחידים כברירת מחדל.
טיפ: אם אתם צריכים להפנות למזהה האפליקציה בקובץ המניפסט, תוכלו להשתמש ב-placeholder ${applicationId}
בכל מאפיין מניפסט. במהלך ה-build, Gradle מחליף את התג הזה במזהה האפליקציה בפועל. מידע נוסף זמין במאמר הזרקת משתני build למניפסט.
שילוב של כמה טעמים של מוצרים עם מימדי טעמים
במקרים מסוימים, יכול להיות שתרצו לשלב הגדרות אישיות מכמה גרסאות של המוצר. לדוגמה, יכול להיות שתרצו ליצור הגדרות שונות למוצרים 'מלא' ו'דמו' שמבוססות על רמת ה-API. כדי לעשות זאת, תוכלו להשתמש בפלאגין Android Gradle כדי ליצור כמה קבוצות של טעמי מוצרים כמאפייני טעמים.
כשמפתחים את האפליקציה, Gradle משלבת הגדרה של טעמים של מוצרים מכל מאפיין של הטעם שהגדרתם, יחד עם הגדרה של סוג ה-build, כדי ליצור את וריאנט ה-build הסופי. Gradle לא משלב בין טעמי מוצרים ששייכים לאותו מאפיין טעם.
דוגמת הקוד הבאה משתמשת במאפיין
flavorDimensions
כדי ליצור מאפיין גרסה מסוג 'מצב'
כדי לקבץ את טעמי המוצרים 'מלא' ו'הדגמה', ובמאפיין גרסת
'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
בנוסף לספריות של קבוצות המקור אפשר ליצור לכל טעם של מוצר ולכל וריאציה של המוצר, אפשר גם ליצור ספריות של קבוצת המקור לכל שילוב של טעמי המוצרים. לדוגמה, אפשר ליצור ולהוסיף מקורות 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 (יצירה של וריאנט) בסרגל התפריטים או על יצירת וריאנטים בסרגל החלון של הכלי.
יצירת קבוצות מקור
כברירת מחדל, מערכת 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 'ניפוי באגים':
------------------------------------------------------------ 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 '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 או משאירים את שם ברירת המחדל.
- בתפריט שלצד 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'. עם זאת, המאפיין 'רמה' באפליקציה כולל טעמים של 'חינם' ו'בתשלום', אבל התלות כוללת רק טעמים של 'הדגמה' ו'בתשלום' עבור אותו מאפיין.
חשוב לזכור: אם יש מאפיין טעימה מסוים שקיים גם באפליקציה וגם בספריות שאליה היא תלויה, אין בעיה אם הספרייה כוללת טעימה של מוצר שלא נכללת באפליקציה. הסיבה לכך היא שהפלאגין אף פעם לא מבקש את הטעם הזה מהתלות.
משתמשים ב-
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'] } } }
יחסי תלות בספרייה כוללים מאפיין של גרסת 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
בחומר העזר על DSL של הפלאגין Android Gradle.
הגדרת הגדרות החתימה
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.