הפלאגין של Android Gradle (AGP) הוא מערכת ה-build הרשמית לאפליקציות ל-Android. הוא כולל תמיכה בהידור של סוגים רבים ושונים של מקורות וקישור שלהם לאפליקציה שאפשר להריץ במכשיר Android פיזי או באמולטור.
ה-AGP מכיל נקודות הרחבה לפלאגינים כדי לשלוט בקלט של ה-build ולהרחיב את הפונקציונליות שלו באמצעות שלבים חדשים שאפשר לשלב עם משימות build רגילות. בגרסאות קודמות של AGP לא היו ממשקי API רשמיים שהופרדו באופן ברור מהטמעות פנימיות. החל מגרסה 7.0, ל-AGP יש קבוצה של ממשקי API רשמיים ויציבים שאפשר להסתמך עליהם.
מחזור החיים של AGP API
AGP פועל לפי מחזור החיים של תכונות Gradle כדי לציין את המצב של ממשקי ה-API שלו:
- פנימי: לא מיועד לשימוש ציבורי
- בשלבי פיתוח: זמינים לשימוש ציבורי אבל לא סופיים, כלומר יכול להיות שהם לא יהיו תואמים לדורות קודמים בגרסה הסופית
- ציבורי: זמין לשימוש ציבורי ויציב
- הוצאו משימוש: אין יותר תמיכה, והם הוחלפו בממשקי API חדשים
מדיניות הוצאה משימוש
ה-AGP מתפתח עם הוצאה משימוש של ממשקי API ישנים והחלפתם בממשקי API חדשים ויציבים ובשפה חדשה ספציפית לדומיין (DSL). השינוי הזה יתפרס על פני כמה גרסאות של AGP. מידע נוסף זמין בציר הזמן של המעבר ל-AGP API/DSL.
כשממשקי API של AGP יוצאים משימוש, במסגרת ההעברה הזו או מסיבה אחרת, הם ימשיכו להיות זמינים בגרסה הראשית הנוכחית אבל יפיקו אזהרות. ממשקי API שהוצאו משימוש יוסרו לחלוטין מ-AGP בגרסה הראשית הבאה. לדוגמה, אם ממשק API הוצא משימוש ב-AGP 7.0, הוא יהיה זמין בגרסה הזו ויוצגו אזהרות. ה-API הזה לא יהיה זמין יותר ב-AGP 8.0.
במתכונים של הפלאגין של Android Gradle אפשר לראות דוגמאות לממשקי API חדשים שמשמשים להתאמות אישיות נפוצות של גרסאות build. הם מספקים דוגמאות להתאמות אישיות נפוצות של גרסאות build. במאמרי העזרה מפורטים פרטים נוספים על ממשקי ה-API החדשים.
הידור מול פריט המידע שנוצר (artifact) של AGP API
כדי לכתוב פלאגין מותאם אישית של Gradle או לוגיקת build (למשל בספרייה buildSrc, מודול פלאגין של קונבנציה או פרויקט פלאגין עצמאי) שמרחיבים את AGP, צריך לקמפל מול פריט המידע שנוצר (artifact) של gradle-api של AGP במקום פריט המידע שנוצר (artifact) של הטמעת הפלאגין המלאה.
למה לקמפל מול פריט המידע שנוצר בתהליך פיתוח התוכנה (Artifact) של gradle-api?
ה-AGP מפרסם שני פריטי מידע עיקריים:
-
com.android.tools.build:gradle-api: מכיל ממשקי AGP API רשמיים, כולל ממשקי API יציבים (כמוAndroidComponentsExtension,Variantוממשקי DSL ציבוריים), ממשקי API בשלבי פיתוח (מסומנים ב-@Incubating) וממשקי API שהוצאו משימוש (מסומנים ב-@Deprecated). -
com.android.tools.build:gradle: מכיל את ההטמעה המלאה של סביבת זמן הריצה של AGP, כולל מחלקות פנימיות, משימות build ושילובים של קומפיילרים.
הידור מול פריט המידע שנוצר בתהליך הפיתוח (Artifact) של gradle-api מספק כמה יתרונות מרכזיים:
- הימנעות מממשקי API פנימיים: כדי לוודא שקוד הפלאגין ניגש רק לממשקי API רשמיים. כך נמנעת תלות מקרית במחלקות פנימיות של הטמעה (
com.android.build.gradle.internal.*) שיכולות להשתנות או להיות מוסרות בגרסאות AGP בלי אזהרה. - תהליכי build מהירים יותר וטביעת רגל קטנה יותר: לארטיפקט
gradle-apiיש עץ תלויות קטן בהרבה מזה של הארטיפקט המלאgradle. כך אפשר להקטין את גודל ההורדה, להימנע מ-classpath pollution ולהאיץ את הקומפילציה של לוגיקת ה-build. - בידוד בזמן ריצה: פלאגינים בהתאמה אישית דורשים רק הגדרות API במהלך ההידור. בזמן הריצה, ה-build של Android מספק את ההטמעה המלאה של AGP כשמפעילים את הפלאגין של האפליקציה ל-Android או הספרייה ל-Android.
הוספת התלות
מוסיפים את פריט המידע שנוצר בתהליך הפיתוח (Artifact) של gradle-api כתלות בקובץ ה-build של פרויקט הפלאגין או בספרייה buildSrc. רכיב ה-Artifact של gradle-api מתפרסם במאגר Maven של Google. כדי לפתור את הבעיה, ודאו שה-build כולל את המאגר google(). לדוגמה, מגדירים אותו בבלוק dependencyResolutionManagement.repositories של הקובץ settings.gradle או settings.gradle.kts, או בבלוק repositories של קובץ ה-build של הפלאגין או של קובץ ה-build של הספרייה buildSrc.
עם קטלוג גרסאות
אם בפרויקט שלכם נעשה שימוש בקטלוג גרסאות, צריך להגדיר את התלות ב-gradle-api בקובץ gradle/libs.versions.toml:
[versions]
androidGradlePlugin = "9.4.0"
[libraries]
android-gradle-plugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" }
הכינוי android-gradle-plugin-api של הקטלוג יוצר את ה-accessor למניעת שגיאות הקלדה libs.android.gradle.plugin.api.
לאחר מכן, מוסיפים את libs.android.gradle.plugin.api לבלוק dependencies של קובץ ה-build של הפלאגין:
Kotlin
dependencies { compileOnly(libs.android.gradle.plugin.api) }
מגניב
dependencies { compileOnly libs.android.gradle.plugin.api }
בלי קטלוג גרסאות
אם בפרויקט שלכם לא נעשה שימוש בקטלוג גרסאות, צריך להצהיר על התלות ישירות בקובץ ה-build של הפלאגין:
Kotlin
dependencies { compileOnly("com.android.tools.build:gradle-api:9.4.0") }
מגניב
dependencies { compileOnly 'com.android.tools.build:gradle-api:9.4.0' }
השתמשו באפשרות ההגדרה compileOnly כברירת המחדל לפלאגינים עצמאיים שמיועדים לפרסום. הפרויקט שמשתמש בתוסף מחיל את AGP ומספק את מחלקות זמן הריצה שלו ב-classpath של buildscript. שימוש בהגדרה compileOnly מונע מהפלאגין שפורסם להדליף תלות בזמן ריצה בגרסת AGP ספציפית, ומונע התנגשויות בין גרסאות אצל הצרכנים. אם אתם מפתחים לוגיקה פנימית של גרסאות build, כמו פלאגינים בספרייה buildSrc או פלאגינים של קונבנציות ב-builds מורכבים, השתמשו באפשרות ההגדרה implementation. הפלאגינים האלה פועלים בתוך ה-build ודורשים AGP ב-classpath של זמן הריצה שלהם.
יסודות ה-build ב-Gradle
המדריך הזה לא מכסה את כל מערכת ה-build של Gradle. עם זאת, הוא כולל את קבוצת המושגים המינימלית שדרושה כדי לעזור לכם לשלב את ממשקי ה-API שלנו, וקישורים למסמכי התיעוד הראשיים של Gradle לקריאה נוספת.
אנחנו מניחים שיש לכם ידע בסיסי על אופן הפעולה של Gradle, כולל איך להגדיר פרויקטים, לערוך קובצי build, להחיל פלאגינים ולהריץ משימות. כדי ללמוד על העקרונות הבסיסיים של Gradle ביחס ל-AGP, מומלץ לעיין במאמר הגדרת ה-build. במאמר Developing Custom Gradle Plugins מוסבר על המסגרת הכללית להתאמה אישית של פלאגינים של Gradle.
מילון מונחים של סוגי Gradle עצלים
ב-Gradle יש כמה סוגים שפועלים באופן "עצל" או עוזרים לדחות חישובים כבדים או יצירת Task לשלבים מאוחרים יותר של ה-build. הסוגים האלה הם הבסיס של הרבה ממשקי API של Gradle ו-AGP. הרשימה הבאה כוללת את הסוגים העיקריים של Gradle שמעורבים בהפעלה מושהית, ואת השיטות העיקריות שלהם.
Provider<T>- מספק ערך מסוג
T(כאשר "T" מייצג כל סוג), שאפשר לקרוא במהלך שלב הביצוע באמצעותget()או להמיר ל-Provider<S>חדש (כאשר "S" מייצג סוג אחר) באמצעות השיטותmap(),flatMap()ו-zip(). שימו לב שאף פעם לא צריך לקרוא ל-get()במהלך שלב ההגדרה.-
map(): מקבלת lambda ומפיקהProviderמסוגS, Provider<S>. הארגומנט של פונקציית הלמבדה ב-map()מקבל את הערךTומפיק את הערךS. הלמבדה לא מופעלת מיד, אלא ההפעלה שלה נדחית לרגע שבו מתבצעת קריאה ל-get()ב-Provider<S>שנוצר, וכך כל השרשרת מופעלת רק כשצריך. -
flatMap(): מקבל גם פונקציית lambda ומפיקProvider<S>, אבל פונקציית ה-lambda מקבלת ערךTומפיקהProvider<S>(במקום להפיק את הערךSישירות). משתמשים ב-flatMap() כשאי אפשר לקבוע את S בזמן ההגדרה, ואפשר לקבל רקProvider<S>. בפועל, אם השתמשתם ב-map()וקיבלתם תוצאה מסוגProvider<Provider<S>>, כנראה שהייתם צריכים להשתמש ב-flatMap(). -
zip(): מאפשר לשלב שני מופעים שלProviderכדי ליצורProviderחדש, עם ערך שמחושב באמצעות פונקציה שמשלבת את הערכים משני המופעים שלProvidersשהוזנו.
-
Property<T>- מטמיע את
Provider<T>, ולכן הוא גם מספק ערך מסוגT. בניגוד ל-Provider<T>, שהוא לקריאה בלבד, אתם יכולים גם להגדיר ערך ל-Property<T>. יש שתי דרכים לעשות את זה:- מגדירים ערך מסוג
Tישירות כשהוא זמין, בלי צורך בחישובים מושהים. - מגדירים
Provider<T>אחר כמקור הערך שלProperty<T>. במקרה הזה, הערךTנוצר רק כשמתבצעת קריאה ל-Property.get().
- מגדירים ערך מסוג
TaskProvider- מטמיע את
Provider<Task>. כדי ליצורTaskProvider, צריך להשתמש ב-tasks.register()ולא ב-tasks.create(), כדי לוודא שהמשימות נוצרות רק כשצריך אותן. אתם יכולים להשתמש ב-flatMap()כדי לגשת לפלט שלTaskלפני שנוצרTask. זה יכול להיות שימושי אם אתם רוצים להשתמש בפלט כקלט למכונות אחרות שלTask.
ספקים ושיטות הטרנספורמציה שלהם חיוניים להגדרה של קלט ופלט של משימות באופן עצל, כלומר בלי ליצור את כל המשימות מראש ולפתור את הערכים.
הספקים גם מכילים מידע על תלות בין משימות. כשיוצרים Provider על ידי שינוי של פלט Task, ה-Task הופך לתלות משתמעת של ה-Provider, והוא ייווצר ויופעל בכל פעם שהערך של ה-Provider יתקבל, למשל כש-Task אחר ידרוש אותו.
בדוגמה הבאה מוצג רישום של שתי משימות, GitVersionTask ו-ManifestProducerTask, תוך דחיית היצירה של המופעים של Task עד שהם נדרשים בפועל. ערך הקלט ManifestProducerTask מוגדר ל-Provider שמתקבל מהפלט של GitVersionTask, ולכן ManifestProducerTask תלוי באופן משתמע ב-GitVersionTask.
// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
it.gitVersionOutputFile.set(
File(project.buildDir, "intermediates/gitVersionProvider/output")
)
}
...
/**
* Register another task in the configuration block (also executed lazily,
* only if the task is required).
*/
val manifestProducer =
project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
/**
* Connect this task's input (gitInfoFile) to the output of
* gitVersionProvider.
*/
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
שתי המשימות האלה יבוצעו רק אם הן יתבקשו במפורש. זה יכול לקרות כחלק מהפעלה של Gradle, למשל אם מריצים את ./gradlew
debugManifestProducer, או אם הפלט של ManifestProducerTask מחובר למשימה אחרת והערך שלו נדרש.
אתם יכולים לכתוב משימות בהתאמה אישית שצורכות קלט ו/או מייצרות פלט, אבל AGP לא מציע גישה ציבורית ישירה למשימות שלו. הם פרט הטמעה שעשוי להשתנות מגרסה לגרסה. במקום זאת, AGP מציע את Variant API וגישה לפלט של המשימות שלו, או פריטי build, שאפשר לקרוא ולשנות. למידע נוסף, קראו את הקטע Variant API, פריטי מידע שנוצר ומשימות במאמר הזה.
שלבי ה-build של Gradle
בניית פרויקט היא תהליך מורכב שדורש הרבה משאבים, ויש תכונות שונות כמו הימנעות מהגדרת משימות, בדיקות עדכניות ותכונת שמירת ההגדרות במטמון שעוזרות לצמצם את הזמן שמושקע בחישובים שניתן לשחזר או בחישובים מיותרים.
כדי להחיל חלק מהאופטימיזציות האלה, הסקריפטים והפלאגינים של Gradle חייבים לפעול לפי כללים מחמירים בכל אחד משלבי ה-build השונים של Gradle: אתחול, הגדרה והפעלה. במדריך הזה נתמקד בשלבי ההגדרה והביצוע. מידע נוסף על כל השלבים זמין במדריך למחזור החיים של ה-build ב-Gradle.
שלב ההגדרה
בשלב ההגדרה, המערכת מעריכה את הסקריפטים של ה-build לכל הפרויקטים שכלולים ב-build, מחילה את הפלאגינים ופותרת את יחסי התלות של ה-build. בשלב הזה צריך להגדיר את ה-build באמצעות אובייקטים של DSL ולרשום משימות ואת הקלט שלהן באופן עצל.
שלב ההגדרה תמיד פועל, לא משנה איזו משימה מבקשים להריץ. לכן חשוב במיוחד לשמור על שלב הגדרה פשוט, ולהגביל את החישובים כך שלא יהיו תלויים בקלט אחר מלבד סקריפטים של ה-build עצמם.
כלומר, לא כדאי להריץ תוכנות חיצוניות או לקרוא מהרשת, או לבצע חישובים ארוכים שאפשר לדחות לשלב ההרצה כמו מכונות Task מתאימות.
שלב הביצוע
בשלב הביצוע, המשימות המבוקשות והמשימות שתלויות בהן מבוצעות. באופן ספציפי, השיטות של המחלקה Task שמסומנות ב-@TaskAction מופעלות. במהלך ביצוע המשימה, מותר לקרוא מקלט (כמו קבצים) ולפתור ספקים עצלים על ידי קריאה ל-Provider<T>.get(). פתרון ספקים עצלים בדרך הזו מתחיל רצף של קריאות map() או flatMap() שעוקבות אחר פרטי התלות של המשימה שכלולים בספק. המשימות מופעלות באופן עצל כדי לממש את הערכים הנדרשים.
Variant API, Artifacts, and Tasks
Variant API הוא מנגנון הרחבה בפלאגין של Android Gradle שמאפשר לשנות את האפשרויות השונות שמשפיעות על ה-build של Android, שבדרך כלל מוגדרות באמצעות DSL בקובצי תצורת ה-build. Variant API גם נותן לכם גישה לקובצי ביניים ולקובצי פלט סופיים שנוצרים על ידי ה-build, כמו קובצי כיתות, המניפסט שעבר מיזוג או קובצי APK או AAB.
תהליך ה-build של Android ונקודות ההרחבה
כשמקיימים אינטראקציה עם AGP, משתמשים בנקודות הרחבה שנוצרו במיוחד במקום לרשום את הקריאות החוזרות (callbacks) הרגילות במחזור החיים של Gradle (כמו afterEvaluate()) או להגדיר יחסי תלות מפורשים של Task. משימות שנוצרות על ידי AGP נחשבות לפרטי הטמעה ולא נחשפות כ-API ציבורי. אל תנסו לקבל מופעים של אובייקטים מסוג Task, לנחש את השמות של Task או להוסיף קריאות חוזרות (callbacks) או יחסי תלות לאובייקטים האלה מסוג Task באופן ישיר.
AGP מבצע את השלבים הבאים כדי ליצור ולהפעיל את המכונות של Task, שיוצרות את פריטי ה-build. אחרי השלבים העיקריים ליצירת אובייקטים ב-Variant, יש קריאות חוזרות (callbacks) שמאפשרות לבצע שינויים באובייקטים מסוימים שנוצרו כחלק מה-build. חשוב לציין שכל הקריאות החוזרות מתרחשות במהלך שלב ההגדרה (שמתואר בדף הזה) והן צריכות לפעול במהירות. במקום זאת, צריך לדחות כל עבודה מורכבת למופעים מתאימים של Task במהלך שלב ההפעלה.
- ניתוח DSL: בשלב הזה מתבצעת הערכה של סקריפטים של build, ונוצרים ומוגדרים המאפיינים השונים של אובייקטים של Android DSL מהבלוק
android. בשלב הזה נרשמים גם פונקציות ה-callback של Variant API שמתוארות בקטעים הבאים.
finalizeDsl(): קריאה חוזרת (callback) שמאפשרת לשנות אובייקטים של DSL לפני שהם ננעלים ליצירת רכיבים (וריאנטים). אובייקטים מסוגVariantBuilderנוצרים על סמך הנתונים שכלולים באובייקטים של DSL.נעילת DSL: ה-DSL נעול עכשיו ואי אפשר לבצע שינויים.
beforeVariants(): הקריאה החוזרת הזו יכולה להשפיע על הרכיבים שנוצרים ועל חלק מהמאפיינים שלהם, באמצעותVariantBuilder. הוא עדיין מאפשר לבצע שינויים בתהליך ה-build ובפריטי המידע שנוצרים.יצירת וריאנט: רשימת הרכיבים והפריטים שייווצרו היא סופית ואי אפשר לשנות אותה.
onVariants(): ב-callback הזה, אתם מקבלים גישה לאובייקטים שנוצרו מסוגVariantואתם יכולים להגדיר ערכים או ספקים לערכיPropertyשהם מכילים, כדי שהם יחושבו באופן עצל.נעילת וריאנטים: אובייקטים של וריאנטים נעולים עכשיו ואי אפשר לשנות אותם.
יצירת משימות: אובייקטים מסוג
Variantוהערכים שלהם מסוגPropertyמשמשים ליצירת המכונות מסוגTaskשנדרשות לביצוע ה-build.
ב-AGP יש AndroidComponentsExtension חדש שמאפשר לרשום קריאות חוזרות (callbacks) ל-finalizeDsl(), ל-beforeVariants() ול-onVariants().
התוסף זמין בסקריפטים של build דרך הבלוק androidComponents:
// This is used only for configuring the Android build through DSL.
android { ... }
// The androidComponents block is separate from the DSL.
androidComponents {
finalizeDsl { extension ->
...
}
}
עם זאת, אנחנו ממליצים להשתמש בסקריפטים של build רק להגדרה הצהרתית באמצעות ה-DSL של הבלוק של Android, ולהעביר כל לוגיקה אימפרטיבית מותאמת אישית לספרייה buildSrc או לפלאגינים חיצוניים. אתם יכולים גם לעיין בדוגמאות של buildSrc במאגר GitHub של מתכוני Gradle כדי ללמוד איך ליצור פלאגין בפרויקט. מוודאים שהפלאגין מבצע קומפילציה מול פריט המידע שנוצר (artifact) של gradle-api בקובץ ה-build. הנה דוגמה לרישום של פונקציות ה-callback מקוד הפלאגין:
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
...
}
}
}
בואו נבחן מקרוב את פונקציות ה-callback הזמינות ואת סוגי התרחישים לדוגמה שהפלאגין יכול לתמוך בהם בכל אחת מהן:
finalizeDsl(callback: (DslExtensionT) -> Unit)
ב-callback הזה, אתם יכולים לגשת לאובייקטים של DSL שנוצרו על ידי ניתוח המידע מהבלוק android בקובצי ה-build, ולשנות אותם.
אובייקטי ה-DSL האלה ישמשו לאתחול ולהגדרה של וריאנטים בשלבים מאוחרים יותר של ה-build. לדוגמה, אפשר ליצור באופן פרוגרמטי הגדרות חדשות או לשנות מאפיינים, אבל חשוב לזכור שצריך לפתור את כל הערכים בזמן ההגדרה, ולכן הם לא יכולים להסתמך על קלט חיצוני.
אחרי שהקריאה החוזרת מסתיימת, אובייקטי ה-DSL כבר לא שימושיים, ואתם לא צריכים יותר להחזיק הפניות אליהם או לשנות את הערכים שלהם.
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
extension.buildTypes.create("extra").let {
it.isJniDebuggable = true
}
}
}
}
beforeVariants()
בשלב הזה של ה-build, אתם מקבלים גישה לאובייקטים מסוג VariantBuilder, שקובעים את הווריאציות שייווצרו ואת המאפיינים שלהן. לדוגמה, אתם יכולים להשבית באופן פרוגרמטי וריאציות מסוימות, את הבדיקות שלהן, או לשנות ערך של מאפיין (לדוגמה, minSdk) רק עבור וריאציה שנבחרה. בדומה ל-finalizeDsl(), כל הערכים שאתם מספקים צריכים להיות מוגדרים בזמן ההגדרה ולא להיות תלויים בקלט חיצוני. אסור לשנות את האובייקטים של VariantBuilder אחרי שההפעלה של פונקציית ה-callback של beforeVariants() מסתיימת.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
הקריאה החוזרת (callback) של beforeVariants() יכולה לקבל VariantSelector, שאפשר לקבל באמצעות ה-method selector() ב-androidComponentsExtension. אפשר להשתמש בו כדי לסנן רכיבים שמשתתפים בהפעלת הקריאה החוזרת (callback) לפי השם, סוג ה-build או גרסת המוצר שלהם.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
עד שמתבצעת קריאה ל-onVariants(), כל פריטי המידע שייווצרו על ידי AGP כבר נקבעו, כך שאי אפשר יותר להשבית אותם. עם זאת, אפשר לשנות חלק מהערכים שמשמשים למשימות על ידי הגדרתם למאפייני Property באובייקטים של Variant. מכיוון שהערכים של Property יתקבלו רק כשהמשימות של AGP יבוצעו, אפשר לחבר אותם בבטחה לספקים מהמשימות בהתאמה אישית שיבצעו את כל החישובים הנדרשים, כולל קריאה מקלט חיצוני כמו קבצים או הרשת.
// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
// Gather the output when we are in single mode (no multi-apk).
val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }
// Create version code generating task
val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
}
/**
* Wire version code from the task output.
* map() will create a lazy provider that:
* 1. Runs just before the consumer(s), ensuring that the producer
* (VersionCodeTask) has run and therefore the file is created.
* 2. Contains task dependency information so that the consumer(s) run after
* the producer.
*/
mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}
הוספת מקורות שנוצרו ל-build
הפלאגין יכול לתרום כמה סוגים של מקורות שנוצרו, למשל:
- קוד האפליקציה בספרייה
java - משאבי Android בספרייה
res - משאבי Java בספרייה
resources - נכסי Android בספרייה
assets
ב-Sources API מופיעה הרשימה המלאה של המקורות שאפשר להוסיף.
בקטע הקוד הזה אפשר לראות איך מוסיפים תיקיית מקור בהתאמה אישית בשם ${variant.name} לקבוצת המקורות של Java באמצעות הפונקציה addStaticSourceDirectory(). לאחר מכן, ה-toolchain של Android מעבד את התיקייה הזו.
onVariants { variant ->
variant.sources.java?.let { java ->
java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
}
}
פרטים נוספים זמינים במתכון addJavaSource.
בקטע הקוד הזה אפשר לראות איך מוסיפים ספרייה עם משאבי Android שנוצרו ממשימה בהתאמה אישית לקבוצת המקורות res. התהליך דומה לסוגי מקורות אחרים.
onVariants(selector().withBuildType("release")) { variant ->
// Step 1. Register the task.
val resCreationTask =
project.tasks.register<ResCreatorTask>("create${variant.name}Res")
// Step 2. Register the task output to the variant-generated source directory.
variant.sources.res?.addGeneratedSourceDirectory(
resCreationTask,
ResCreatorTask::outputDirectory)
}
...
// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
@get:OutputFiles
abstract val outputDirectory: DirectoryProperty
@TaskAction
fun taskAction() {
// Step 4. Generate your resources.
...
}
}
פרטים נוספים זמינים במתכון addCustomAsset.
גישה ל-Artifacts ושינוי שלהם
בנוסף לאפשרות לשנות מאפיינים פשוטים באובייקטים של Variant, AGP מכיל גם מנגנון הרחבה שמאפשר לקרוא או לשנות פריטי מידע (artifacts) זמניים וסופיים שנוצרו במהלך ה-build. לדוגמה, אתם יכולים לקרוא את התוכן של קובץ AndroidManifest.xml הסופי והממוזג ב-Task בהתאמה אישית כדי לנתח אותו, או להחליף את התוכן שלו לגמרי בתוכן של קובץ מניפסט שנוצר על ידי Task בהתאמה אישית.
רשימת פריטי המידע שנתמכים כרגע מופיעה במאמרי העזרה של המחלקה Artifact. לכל סוג פריט מידע יש מאפיינים מסוימים שכדאי להכיר:
עוצמה (cardinality)
העוצמה של Artifact מייצגת את מספר המופעים של FileSystemLocation, או את מספר הקבצים או הספריות של סוג פריט המידע שנוצר בתהליך הפיתוח (Artifact). כדי לקבל מידע על העוצמה של פריט מידע, אפשר לבדוק את מחלקת ההורה שלו: פריטי מידע עם FileSystemLocation יחיד יהיו מחלקת משנה של Artifact.Single, ופריטי מידע עם כמה מופעים של FileSystemLocation יהיו מחלקת משנה של Artifact.Multiple.
סוג FileSystemLocation
כדי לבדוק אם Artifact מייצג קבצים או ספריות, אפשר להסתכל על סוג FileSystemLocation עם הפרמטרים שלו, שיכול להיות RegularFile או Directory.
פעולות נתמכות
כל מחלקה של Artifact יכולה להטמיע כל אחד מהממשקים הבאים כדי לציין באילו פעולות היא תומכת:
-
Transformable: מאפשר להשתמש ב-Artifactכקלט ל-Taskשמבצע עליו טרנספורמציות שרירותיות ומוציא גרסה חדשה של ה-Artifact. -
Appendable: חל רק על פריטי מידע שנוצרו שהם מחלקות משנה שלArtifact.Multiple. המשמעות היא שאפשר להוסיף ל-Artifact, כלומרTaskבהתאמה אישית יכול ליצור מופעים חדשים של סוג ה-Artifactהזה, שיתווספו לרשימה הקיימת. -
Replaceable: חל רק על פריטי מידע שנוצרו שהם מחלקות משנה שלArtifact.Single. אפשר להחליףArtifactשניתן להחלפה במופע חדש לגמרי, שנוצר כפלט שלTask.
בנוסף לשלוש הפעולות לשינוי פריטים, כל פריט תומך בפעולה get() (או getAll()), שמחזירה Provider עם הגרסה הסופית של הפריט (אחרי שכל הפעולות עליו הסתיימו).
כמה פלאגינים יכולים להוסיף כל מספר של פעולות על פריטי מידע שנוצרו בתהליך הפיתוח (Artifact) לצינור עיבוד הנתונים מהקריאה החוזרת (callback) של onVariants(), ו-AGP יוודא שהם משורשרים בצורה נכונה כדי שכל המשימות יפעלו בזמן הנכון ופריטי המידע שנוצרו בתהליך הפיתוח יופקו ויעודכנו בצורה נכונה. כלומר, כשפעולה משנה פלט כלשהו על ידי הוספה, החלפה או שינוי שלו, הפעולה הבאה תראה את הגרסה המעודכנת של פריטי המידע האלה כקלט, וכן הלאה.
נקודת הכניסה לרישום פעולות היא המחלקה Artifacts.
בקטע הקוד הבא אפשר לראות איך מקבלים גישה למופע של Artifacts ממאפיין באובייקט Variant בקריאה החוזרת (callback) של onVariants().
לאחר מכן, אפשר להעביר את ה-TaskProvider המותאם אישית כדי לקבל אובייקט TaskBasedOperation (1), ולהשתמש בו כדי לחבר את הקלט והפלט שלו באמצעות אחת מהשיטות של wiredWith* (2).
השיטה המדויקת שצריך לבחור תלויה בקרדינליות ובסוג של FileSystemLocation שמוטמע על ידי Artifact שרוצים לשנות.
ולבסוף, מעבירים את הסוג Artifact ל-method שמייצגת את הפעולה שנבחרה באובייקט *OperationRequest שמתקבל בתמורה, לדוגמה, toAppendTo(), toTransform() או toCreate() (3).
androidComponents.onVariants { variant ->
val manifestUpdater = // Custom task that will be used for the transform.
project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
// (1) Register the TaskProvider w.
val variant.artifacts.use(manifestUpdater)
// (2) Connect the input and output files.
.wiredWithFiles(
ManifestTransformerTask::mergedManifest,
ManifestTransformerTask::updatedManifest)
// (3) Indicate the artifact and operation type.
.toTransform(SingleArtifact.MERGED_MANIFEST)
}
בדוגמה הזו, MERGED_MANIFEST הוא SingleArtifact, והוא RegularFile. לכן צריך להשתמש ב-method wiredWithFiles, שמקבלת הפניה אחת של RegularFileProperty לקלט, והפניה אחת של RegularFileProperty לפלט. יש שיטות wiredWith* אחרות במחלקה TaskBasedOperation שיעבדו עם שילובים אחרים של עוצמה Artifact וסוגי FileSystemLocation.
כדי לקבל מידע נוסף על הרחבת AGP, מומלץ לקרוא את הקטעים הבאים במדריך של מערכת ה-build של Gradle:
- פיתוח פלאגינים בהתאמה אישית של Gradle
- הטמעת יישומי פלאגין של Gradle
- פיתוח סוגים של משימות Gradle בהתאמה אישית
- הגדרה עצלה
- הימנעות מהגדרת משימות