פלאגין של Android Gradle (AGP) הוא מערכת ה-build הרשמית לאפליקציות ל-Android. הוא כולל תמיכה בהידור של סוגים רבים ושונים של מקורות וקישור שלהם לאפליקציה שאפשר להריץ במכשיר Android פיזי או באמולטור.
AGP מכיל נקודות הרחבה לתוספים, שמאפשרות לשלוט בקלט של תהליך הבנייה ולהרחיב את הפונקציונליות שלו באמצעות שלבים חדשים שאפשר לשלב עם משימות בנייה רגילות. בגרסאות קודמות של AGP לא היו ממשקי API רשמיים שהופרדו בבירור מהטמעות פנימיות. החל מגרסה 7.0, ל-AGP יש קבוצה של ממשקי API רשמיים ויציבים שאפשר להסתמך עליהם.
מחזור החיים של AGP API
AGP פועל לפי מחזור החיים של התכונות ב-Gradle כדי לציין את המצב של ממשקי ה-API שלו:
- פנימי: לא מיועד לשימוש ציבורי
- בשלבי פיתוח: זמינות לשימוש ציבורי אבל לא סופיות, כלומר יכול להיות שהן לא יהיו תואמות לאחור בגרסה הסופית
- ציבורית: זמינה לשימוש ציבורי ויציבה
- הוצא משימוש: לא נתמך יותר, והוחלף בממשקי API חדשים
מדיניות הוצאה משימוש
AGP מתפתח עם הוצאה משימוש של ממשקי API ישנים והחלפה שלהם בממשקי API חדשים ויציבים ובשפה חדשה ספציפית לדומיין (DSL). השינוי הזה יתבצע בכמה גרסאות של AGP, ומידע נוסף זמין בציר הזמן של המעבר של AGP API/DSL.
כשמוציאים משימוש ממשקי AGP API, לצורך ההעברה הזו או מסיבות אחרות, הם ממשיכים להיות זמינים בגרסה הראשית הנוכחית אבל מוצגות אזהרות לגביהם. ממשקי API שהוצאו משימוש יוסרו לחלוטין מ-AGP בגרסה הראשית הבאה. לדוגמה, אם הוצא משימוש ממשק API ב-AGP 7.0, הוא יהיה זמין בגרסה הזו ויציג אזהרות. ממשק ה-API הזה לא יהיה זמין יותר ב-AGP 8.0.
כדי לראות דוגמאות לשימוש בממשקי API חדשים בהתאמות אישיות נפוצות של build, אפשר לעיין במתכונים לפלאגין של Android Gradle. הם מספקים דוגמאות להתאמות אישיות נפוצות של בנייה. מידע נוסף על ממשקי ה-API החדשים מופיע גם במאמרי העזרה.
קומפילציה מול ארטיפקט של AGP API
כדי לכתוב פלאגין Gradle מותאם אישית או לוגיקת בנייה (למשל בספרייה buildSrc, במודול פלאגין של מוסכמות או בפרויקט פלאגין עצמאי) שמרחיב את AGP, צריך לבצע קומפילציה מול ארטיפקט gradle-api של AGP במקום מול ארטיפקט ההטמעה המלא של הפלאגין.
למה קומפילציה מול ארטיפקט gradle-api?
AGP מפרסם שני ארטיפקטים עיקריים:
-
com.android.tools.build:gradle-api: מכיל ממשקי API רשמיים של AGP, כולל ממשקי API יציבים (כמוAndroidComponentsExtension,Variantוממשקי DSL ציבוריים), ממשקי API בתהליך פיתוח (מסומנים ב-@Incubating) וממשקי API שיצאו משימוש (מסומנים ב-@Deprecated). -
com.android.tools.build:gradle: מכיל את ההטמעה המלאה של זמן הריצה של AGP, כולל מחלקות פנימיות, משימות בנייה ושילובי קומפיילר.
קומפילציה מול ארטיפקט gradle-api מספקת כמה יתרונות מרכזיים:
- הימנעות משימוש בממשקי API פנימיים: האפשרות הזו מבטיחה שקוד הפלאגין יגש רק לממשקי API רשמיים. כך נמנעת תלות לא מכוונת במחלקות הטמעה פנימיות (
com.android.build.gradle.internal.*) שיכולות להשתנות או להימחק בגרסאות שונות של AGP ללא אזהרה. - מהירויות בנייה גבוהות יותר וטביעת רגל קטנה יותר: לארטיפקט
gradle-apiיש עץ תלות קטן בהרבה בהשוואה לארטיפקטgradleהמלא. כך מקטינים את גודל ההורדה, מונעים זיהום של נתיב המחלקה ומאיצים את ההידור של לוגיקת הבנייה. - בידוד בזמן ריצה: פלאגינים בהתאמה אישית דורשים הגדרות API רק במהלך הקומפילציה. בזמן הריצה, ההטמעה המלאה של AGP מסופקת על ידי ה-build של Android כשמחילים את הפלאגין של האפליקציה ל-Android או הספרייה.
הוספת התלות
מוסיפים את ארטיפקט gradle-api כתלות בקובץ ה-build של פרויקט הפלאגין או בספרייה buildSrc. ארטיפקט 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 יוצר את הפונקציה הבטוחה לגישה לנתונים 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 ומספק את מחלקות זמן הריצה שלו בנתיב המחלקה של buildscript. השימוש בהגדרת compileOnly
מונע מהתוסף שפרסמת להדליף תלות בזמן ריצה
בגרסה ספציפית של AGP, ומונע התנגשויות גרסאות אצל הצרכנים. אם אתם מפתחים לוגיקה פנימית לבנייה – כמו פלאגינים בספרייה buildSrc או פלאגינים מוסכמים בבנייה מורכבת – השתמשו באפשרות ההגדרה implementation. הפלאגינים האלה פועלים במהלך ה-build ונדרש AGP בנתיב המחלקה של זמן הריצה שלהם.
הבסיס של בניית Gradle
המדריך הזה לא כולל את כל מערכת ה-build של Gradle. עם זאת, הוא כולל את המושגים המינימליים שצריך להכיר כדי לשלב את ה-API שלנו, וקישורים למסמכי Gradle הראשיים לקריאה נוספת.
אנחנו מניחים שיש לכם ידע בסיסי על אופן הפעולה של Gradle, כולל איך להגדיר פרויקטים, לערוך קובצי build, להחיל פלאגינים ולהריץ משימות. כדי להבין את היסודות של Gradle בהקשר של AGP, מומלץ לעיין במאמר הגדרת ה-build. מידע על המסגרת הכללית להתאמה אישית של פלאגינים של Gradle זמין במאמר בנושא פיתוח פלאגינים מותאמים אישית של Gradle.
מילון מונחים של סוגים עצלים ב-Gradle
ל-Gradle יש כמה סוגים שמתנהגים בצורה "עצלנית" או עוזרים לדחות חישובים כבדים אוTask יצירה לשלבים מאוחרים יותר של הבנייה. הסוגים האלה הם הבסיס להרבה ממשקי API של Gradle ו-AGP. הרשימה הבאה כוללת את הסוגים העיקריים של Gradle שמשתתפים בהפעלה עצלה, ואת השיטות העיקריות שלהם.
Provider<T>- הפונקציה מחזירה ערך מסוג
T(כאשר T מייצג כל סוג), שאפשר לקרוא אותו במהלך שלב ההפעלה באמצעותget()או להמיר אותו לערך חדש מסוגProvider<S>(כאשר S מייצג סוג אחר) באמצעות השיטותmap(),flatMap()ו-zip(). שימו לב שאסור לקרוא ל-get()במהלך שלב ההגדרה.-
map(): מקבל lambda ומפיקProviderמסוגS,Provider<S>. הארגומנט lambda שלmap()מקבל את הערךTומחזיר את הערךS. הביטוי למדא לא מופעל באופן מיידי, אלא ההפעלה שלו נדחית לרגע שבו מתבצעת קריאה ל-get()ב-Provider<S>שמתקבל, וכך כל השרשרת הופכת ל-lazy. -
flatMap(): מקבלת גם פונקציית Lambda ומפיקה את הערךProvider<S>, אבל פונקציית ה-Lambda מקבלת ערךTומפיקה את הערךProvider<S>(במקום להפיק את הערךSישירות). משתמשים ב-flatMap() כשאי אפשר לקבוע את S בזמן ההגדרה ואפשר לקבל רקProvider<S>. מבחינה מעשית, אם השתמשתם ב-map()וקיבלתם תוצאה מסוגProvider<Provider<S>>, כנראה שהייתם צריכים להשתמש ב-flatMap(). -
zip(): מאפשרת לשלב שני מופעים שלProviderכדי ליצורProviderחדש, עם ערך שמחושב באמצעות פונקציה שמשלבת את הערכים משני מופעי הקלט שלProviders.
-
Property<T>
- Implements
Provider<T>, so it also provides a value of typeT. בניגוד ל-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 artifacts, שאפשר לקרוא ולשנות. מידע נוסף זמין במאמר Variant API, Artifacts, and Tasks.
שלבי build של Gradle
בניית פרויקט היא תהליך מורכב שדורש משאבים רבים. יש תכונות שנועדו לצמצם את הזמן שמושקע בחישובים שניתן לשחזר או בחישובים מיותרים, כמו הימנעות מהגדרת משימות, בדיקות עדכניות ותכונת שמירת ההגדרות במטמון.
כדי להחיל חלק מהאופטימיזציות האלה, סקריפטים ותוספים של Gradle צריכים לפעול לפי כללים מחמירים במהלך כל אחד משלבי ה-build הנפרדים של Gradle: אתחול, הגדרה והרצה. במדריך הזה נתמקד בשלבי ההגדרה והביצוע. מידע נוסף על כל השלבים זמין במדריך למחזור החיים של Gradle build.
שלב ההגדרה
במהלך שלב ההגדרה, המערכת מעריכה את סקריפטים הבנייה של כל הפרויקטים שמשתתפים בבנייה, מחילה את התוספים ומזהה את התלות בבנייה. בשלב הזה צריך להגדיר את ה-build באמצעות אובייקטים של DSL, ולרשום משימות ואת הקלט שלהן באופן עצלני.
מכיוון ששלב ההגדרה תמיד מופעל, לא משנה איזו משימה מופעלת, חשוב במיוחד לשמור על יעילותו ולהגביל את החישובים כך שלא יהיו תלויים בקלט שאינו סקריפטים של בנייה.
כלומר, לא מומלץ להפעיל תוכנות חיצוניות או לקרוא מהרשת, או לבצע חישובים ארוכים שאפשר לדחות לשלב ההפעלה כמו מופעים מתאימים של Task.
שלב הביצוע
בשלב הביצוע, המשימות המבוקשות והמשימות שתלויות בהן מבוצעות. באופן ספציפי, מופעלות שיטות המחלקה Task שמסומנות ב-@TaskAction. במהלך ביצוע המשימה, מותר לקרוא נתונים מקלט (כמו קבצים) ולפתור ספקי lazy על ידי קריאה ל-Provider<T>.get(). פתרון של lazy
providers באופן הזה מפעיל רצף של קריאות map() או flatMap() שפועלות לפי מידע התלות במשימה שכלול ב-provider. המשימות מופעלות באופן עצלני כדי ליצור את הערכים הנדרשים.
Variant API, Artifacts ו-Tasks
Variant API הוא מנגנון הרחבה בפלאגין של Android Gradle שמאפשר לכם לשנות את האפשרויות השונות, שבדרך כלל מוגדרות באמצעות DSL בקובצי הגדרת ה-build, שמשפיעות על ה-build של Android. Variant API גם מאפשר לכם לגשת לארטיפקטים ביניים וסופיים שנוצרים על ידי ה-build, כמו קובצי מחלקה, המניפסט הממוזג או קובצי APK/AAB.
תהליך ה-build של Android ונקודות ההרחבה
כשמבצעים אינטראקציה עם AGP, צריך להשתמש בנקודות הרחבה שנוצרו במיוחד במקום לרשום את הקריאות החוזרות (callbacks) הרגילות של מחזור החיים של Gradle (כמו afterEvaluate()) או להגדיר תלות מפורשת ב-Task. משימות שנוצרו על ידי AGP נחשבות לפרטי הטמעה ולא מוצגות כ-API ציבורי. אסור לנסות לקבל מופעים של אובייקטים מסוג Task או לנחש את השמות של Task ולהוסיף קריאות חוזרות או תלות לאובייקטים האלה Task ישירות.
AGP מבצע את השלבים הבאים כדי ליצור ולהפעיל את מופעי Task, שבסופו של דבר יוצרים את תוצרי הבנייה. אחרי השלבים העיקריים של יצירת אובייקט Variant, מתבצעות קריאות חוזרות שמאפשרות לכם לבצע שינויים באובייקטים מסוימים שנוצרו כחלק מהבנייה. חשוב לציין שכל הקריאות החוזרות מתרחשות במהלך שלב ההגדרה (שמתואר בדף הזה) והן חייבות לפעול במהירות. כל עבודה מורכבת יותר צריכה להתבצע על ידי מופעי Task מתאימים במהלך שלב ההפעלה.
- ניתוח של DSL: זה קורה כשמעריכים סקריפטים של build, וכשיוצרים ומגדירים את המאפיינים השונים של אובייקטים של Android DSL מהבלוק
android. במהלך השלב הזה נרשמים גם הקבלה החוזרת (callback) של Variant API שמתוארים בקטעים הבאים.
finalizeDsl(): פונקציית קריאה חוזרת שמאפשרת לשנות אובייקטים של DSL לפני שהם ננעלים ליצירת רכיב (וריאנט). אובייקטים מסוגVariantBuilderנוצרים על סמך נתונים שנכללים באובייקטים של DSL.נעילת DSL: ה-DSL נעול עכשיו ואי אפשר לבצע יותר שינויים.
beforeVariants(): הקריאה החוזרת הזו יכולה להשפיע על הרכיבים שנוצרים ועל חלק מהמאפיינים שלהם, באמצעותVariantBuilder. היא עדיין מאפשרת לבצע שינויים בתהליך הבנייה ובפריטי המידע שנוצרים.יצירת וריאנטים: רשימת הרכיבים והארטיפקטים שייווצרו סופית ואי אפשר לשנות אותה.
onVariants(): ב-callback הזה מקבלים גישה לאובייקטיםVariantשנוצרו, ואפשר להגדיר ערכים או ספקים לערכיםPropertyשהם מכילים, כדי שהחישוב יתבצע רק כשצריך.נעילת וריאנטים: אובייקטים של וריאנטים נעולים עכשיו ואי אפשר לבצע בהם שינויים.
משימות שנוצרו: נעשה שימוש באובייקטים
VariantובערכיםPropertyשלהם כדי ליצור את מופעיTaskשנדרשים לביצוע הבנייה.
AGP מציג את AndroidComponentsExtension שמאפשר לרשום קריאות חוזרות (callback) ל-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-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)
בפונקציית הקריאה החוזרת הזו, אפשר לגשת לאובייקטים של 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()
בשלב הזה של הבנייה, מקבלים גישה לאובייקטים של VariantBuilder, שקובעים את הווריאציות שייווצרו ואת המאפיינים שלהן. לדוגמה,
אפשר להשבית באופן פרוגרמטי וריאציות מסוימות, את הבדיקות שלהן או לשנות את הערך של מאפיין (למשל, minSdk) רק עבור וריאציה נבחרת. בדומה ל-finalizeDsl(), כל הערכים שאתם מספקים צריכים להיות מוגדרים בזמן ההגדרה ולא להיות תלויים בקלט חיצוני. אסור לשנות את האובייקטים VariantBuilder אחרי שהפונקציה beforeVariants() callback מסיימת את ההרצה.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
הקריאה החוזרת beforeVariants() יכולה לקבל אופציונלית VariantSelector, שאפשר לקבל באמצעות המתודה selector() ב-androidComponentsExtension. אפשר להשתמש בו כדי לסנן רכיבים שמשתתפים בהפעלת הקריאה החוזרת על סמך השם, סוג ה-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() })
}
הוספת מקורות שנוצרו על ידי AI לבנייה
התוסף יכול ליצור כמה סוגים של מקורות שנוצרו על ידי AI, כמו:
- קוד האפליקציה בספרייה
java - משאבי Android בספרייה
res - Java resources
בספרייה
resources - נכסי Android בספרייה
assets
רשימה מלאה של המקורות שאפשר להוסיף מופיעה במאמר בנושא Sources API.
בקטע הקוד הזה אפשר לראות איך מוסיפים ל-Java source set תיקיית מקור בהתאמה אישית בשם ${variant.name} באמצעות הפונקציה addStaticSourceDirectory(). לאחר מכן, ערכת הכלים של Android מעבדת את התיקייה הזו.
onVariants { variant ->
variant.sources.java?.let { java ->
java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
}
}
פרטים נוספים זמינים במאמר בנושא addJavaSource recipe.
בקטע הקוד הזה אפשר לראות איך להוסיף ספרייה עם משאבי 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 recipe.
גישה לפריטי מידע שנוצרים בתהליך פיתוח (Artifact) ושינוי שלהם
בנוסף לאפשרות לשנות מאפיינים פשוטים באובייקטים Variant, AGP כולל גם מנגנון הרחבה שמאפשר לקרוא או לשנות ארטיפקטים ביניים וארטיפקטים סופיים שנוצרו במהלך הבנייה. לדוגמה, אתם יכולים לקרוא את התוכן של קובץ AndroidManifest.xml סופי וממוזג בTask בהתאמה אישית כדי לנתח אותו, או להחליף את התוכן שלו לגמרי בתוכן של קובץ מניפסט שנוצר על ידי Task בהתאמה אישית.
אפשר למצוא את רשימת הארטיפקטים שנתמכים כרגע במסמכי העזר של המחלקה Artifact. לכל סוג של ארטיפקט יש מאפיינים מסוימים שכדאי להכיר:
עוצמה (cardinality)
העוצמה (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 עם הגרסה הסופית של הארטיפקט (אחרי שכל הפעולות בו הסתיימו).
כמה תוספים יכולים להוסיף כל מספר של פעולות על ארטיפקטים לצינור הנתונים מהקריאה החוזרת onVariants(), ו-AGP יוודא שהן משורשרות בצורה נכונה כדי שכל המשימות יפעלו בזמן הנכון והארטיפקטים ייווצרו ויתעדכנו בצורה נכונה. המשמעות היא שכאשר פעולה משנה פלטים על ידי הוספה, החלפה או שינוי שלהם, הפעולה הבאה תראה את הגרסה המעודכנת של הארטיפקטים האלה כקלט, וכן הלאה.
נקודת הכניסה לרישום פעולות היא המחלקה Artifacts.
בקטע הקוד הבא אפשר לראות איך מקבלים גישה למופע של Artifacts ממאפיין באובייקט Variant בקריאה החוזרת onVariants().
לאחר מכן אפשר להעביר את TaskProvider המותאם אישית כדי לקבל אובייקט TaskBasedOperation (1), ולהשתמש בו כדי לחבר את הקלט והפלט שלו באמצעות אחת מהשיטות של wiredWith* (2).
השיטה המדויקת שצריך לבחור תלויה בקרדינליות ובFileSystemLocationסוג שהוטמעו על ידי Artifact שרוצים לשנות.
לבסוף, מעבירים את סוג Artifact לשיטה שמייצגת את הפעולה שנבחרה באובייקט *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. לכן, צריך להשתמש בשיטה wiredWithFiles, שמקבלת הפניה אחת ל-RegularFileProperty כקלט ו-RegularFileProperty אחד כפלט. יש עוד wiredWith* שיטות במחלקה TaskBasedOperation שיכולות להתאים לשילובים אחרים של Artifact קרדינליות וסוגים של FileSystemLocation.
כדי לקבל מידע נוסף על הרחבת AGP, מומלץ לקרוא את הקטעים הבאים במדריך למערכת ה-build של Gradle:
- פיתוח פלאגינים מותאמים אישית של Gradle
- הטמעה של פלאגינים של Gradle
- פיתוח סוגים מותאמים אישית של משימות Gradle
- הגדרה של Lazy Configuration
- Task Configuration Avoidance