כתיבת יישומי פלאגין של Gradle

הפלאגין של Android Gradle‏ (AGP) הוא מערכת ה-build הרשמית לאפליקציות Android. היא כוללת תמיכה בתכנות של סוגים רבים ושונים של מקורות, וקישור שלהם יחד לאפליקציה שאפשר להריץ במכשיר Android פיזי או במהדמ.

AGP מכיל נקודות הרחבה לתוספים, שמאפשרות לשלוט בנתוני הקלט של ה-build ולהרחיב את הפונקציונליות שלו באמצעות שלבים חדשים שאפשר לשלב עם משימות build רגילות. בגרסאות קודמות של AGP, ממשקי ה-API הרשמיים לא היו מופרדים בבירור מהטמעות פנימיות. החל מגרסה 7.0, ל-AGP יש קבוצה של ממשקי API רשמיים ויציבים שאפשר להסתמך עליהם.

מחזור החיים של API ב-AGP

AGP פועל לפי מחזור החיים של תכונות ב-Gradle כדי לקבוע את המצב של ממשקי ה-API שלו:

  • פנימי: לא מיועד לשימוש ציבורי
  • בשלבי פיתוח: זמינות לשימוש ציבורי אבל לא סופיות, כלומר יכול להיות שהן לא יהיו תואמות לאחור בגרסה הסופית.
  • גלויה לכולם: זמינה לשימוש ציבורי ויציבה
  • הוצאו משימוש: אין יותר תמיכה בהם, והם הוחלפו בממשקי API חדשים

מדיניות הוצאה משימוש

AGP מתפתח עם הוצאה משימוש של ממשקי API ישנים והחלפתם בממשקי API חדשים ויציבים ובשפה חדשה לתחום ספציפי (DSL). התהליך הזה יתפרס על פני כמה גרסאות של AGP. מידע נוסף זמין בלוח הזמנים להעברה של AGP API/DSL.

כשממשקי ה-API של AGP יוצאו משימוש, במסגרת ההעברה הזו או מסיבות אחרות, הם ימשיכו להיות זמינים בגרסה הראשית הנוכחית, אבל יופיעו אזהרות. ממשקי ה-API שהוצאו משימוש יוסרו לחלוטין מ-AGP במהדורה הראשית הבאה. לדוגמה, אם ממשק API הוצא משימוש ב-AGP 7.0, הוא יהיה זמין בגרסה הזו ויופיעו אזהרות. ממשק ה-API הזה לא יהיה זמין יותר ב-AGP 8.0.

כדי לראות דוגמאות לממשקי API חדשים שמשמשים בהתאמות אישיות נפוצות של גרסאות build, אפשר לעיין במתכונים של הפלאגין של Android Gradle. הן כוללות דוגמאות להתאמות אישיות נפוצות של גרסאות build. פרטים נוספים על ממשקי ה-API החדשים זמינים במסמכי העזרה שלנו.

יסודות של Gradle build

המדריך הזה לא מכסה את כל מערכת ה-build של Gradle. עם זאת, הוא מכיל את קבוצת המושגים המינימלית הנדרשת כדי לעזור לכם לשלב את ממשקי ה-API שלנו, ויש בו קישורים למסמכים הראשיים של Gradle לקריאה נוספת.

אנחנו מניחים שיש לכם ידע בסיסי לגבי אופן הפעולה של Gradle, כולל איך להגדיר פרויקטים, לערוך קובצי build, להחיל יישומי פלאגין ולהריץ משימות. כדי לקבל מידע בסיסי על Gradle ביחס ל-AGP, מומלץ לעיין במאמר הגדרת ה-build. במאמר פיתוח יישומי פלאגין מותאמים אישית ל-Gradle מוסבר על המסגרת הכללית להתאמה אישית של יישומי פלאגין ל-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>. הארגומנט של פונקציית ה-lambda ל-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,‏ Artifacts and Tasks במסמך הזה.

שלבי ה-build ב-Gradle

פיתוח פרויקט הוא תהליך מורכב שמצריך משאבים רבים, אבל יש תכונות שונות כמו הימנעות מהגדרת משימות, בדיקות עדכניות ותכונה של שמירת הגדרות במטמון, שעוזרות לצמצם את הזמן שדרוש לחישובים שניתן לשחזור או לחישובים מיותרים.

כדי להחיל חלק מהאופטימיזציות האלה, הסקריפטים והיישומי הפלאגין של Gradle חייבים לציית לכללים מחמירים בכל אחד משלושת שלבי ה-build של Gradle: אתחול, הגדרה והרצה. במדריך הזה נתמקד בשלבים של ההגדרה וההפעלה. מידע נוסף על כל השלבים זמין במדריך למחזור החיים של ה-build ב-Gradle.

שלב ההגדרה

בשלב ההגדרה, מתבצעת הערכה של סקריפטים ל-build בכל הפרויקטים שנכללים ב-build, הופעל הפלאגינים ופותרים את יחסי התלות ב-build. בשלב הזה צריך להגדיר את ה-build באמצעות אובייקטים של DSL, ולרשום משימות ואת הקלט שלהן באופן עצל.

מכיוון ששלב ההגדרה פועל תמיד, ללא קשר למשימה שמבקשים להריץ, חשוב במיוחד לשמור עליו פשוט ולהגביל את החישובים כך שלא יהיו תלויים בנתונים שמגיעים ממקורות אחרים מלבד סקריפטים של build עצמם. כלומר, אסור להריץ תוכנות חיצוניות, לקרוא מהרשת או לבצע חישובים ארוכים שאפשר לדחות לשלב הביצוע כמכונות Task רגילות.

שלב הביצוע

בשלב הביצוע מתבצעות המשימות המבוקשות והמשימות התלויות בהן. באופן ספציפי, מתבצעת ההפעלה של שיטות הכיתה Task שמסומנות ב-@TaskAction. במהלך ביצוע המשימה, מותר לקרוא מהקלטים (כמו קבצים) ולפתור ספקים עצלים באמצעות קריאה ל-Provider<T>.get(). פתרון ספקים עצלים בדרך הזו מפעיל רצף של קריאות ל-map() או ל-flatMap(), בהתאם למידע על יחסי התלות בין המשימות שמכיל הספק. המשימות מופעלות באיטרציה כדי להפוך את הערכים הנדרשים לממשיים.

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, שמפיקות את ארטיפקטי ה-build. אחרי השלבים העיקריים של יצירת האובייקט Variant, מגיעים קריאות חזרה (callbacks) שמאפשרות לבצע שינויים באובייקטים מסוימים שנוצרו כחלק מ-build. חשוב לציין שכל הקריאות החוזרות מתבצעות במהלך שלב ההגדרה (המתואר בדף הזה), והן צריכות לפעול במהירות, ולהעביר משימות מורכבות למכונות Task מתאימות במהלך שלב הביצוע.

  1. ניתוח של שפת DSL: בשלב הזה מתבצעת הערכה של סקריפטים ל-build, וכן היצירה וההגדרה של המאפיינים השונים של אובייקטי ה-DSL של Android מהבלוק android. גם קריאות החזרה (callbacks) של Variant API שמתוארות בקטעים הבאים נרשמות בשלב הזה.
  2. finalizeDsl(): קריאה חוזרת (callback) שמאפשרת לשנות אובייקטים של DSL לפני שהם ננעלים ליצירת רכיב (וריאנט). אובייקטים מסוג VariantBuilder נוצרים על סמך נתונים שמכילים אובייקטים של DSL.

  3. נעילת DSL: ה-DSL נעול עכשיו ואי אפשר לבצע בו שינויים.

  4. beforeVariants(): קריאה חוזרת (callback) זו יכולה להשפיע על הרכיבים שייווצרו ועל חלק מהמאפיינים שלהם באמצעות VariantBuilder. עדיין אפשר לבצע שינויים בתהליך ה-build ובפריטי ה-artifact שנוצרים.

  5. יצירת וריאנטים: רשימת הרכיבים והפריטים שייווצרו תהיה סופית ולא ניתן יהיה לשנות אותה.

  6. onVariants(): בקריאה החוזרת הזו אתם מקבלים גישה לאובייקטים מסוג Variant שנוצרו, ואתם יכולים להגדיר ערכים או ספקים לערכים של Property שהם מכילים, כדי שהם יחושבו באופן עצל.

  7. נעילת הווריאציות: אובייקטים של וריאציות נעולים עכשיו ואי אפשר לבצע בהם שינויים.

  8. משימות שנוצרו: אובייקטים מסוג 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 דוגמא במאגר של מתכוני Gradle ב-GitHub כדי ללמוד איך ליצור פלאגין בפרויקט. דוגמה לרישום של קריאות החזרה (callbacks) מקוד הפלאגין:

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            ...
        }
    }
}

נבחן מקרוב את קריאות ה-back-call הזמינות ואת סוגי התרחישים לדוגמה שבהם הפלאגין יכול לתמוך בכל אחת מהן:

finalizeDsl(callback: (DslExtensionT) -> Unit)

ב-callback הזה אפשר לגשת לאובייקטים של ה-DSL שנוצרו על ידי ניתוח המידע מהבלוק android בקובצי ה-build ולשנות אותם. אובייקטי ה-DSL האלה ישמשו להפעלה ולהגדרה של וריאנטים בשלבים מאוחרים יותר של ה-build. לדוגמה, אפשר ליצור באופן פרוגרמטי הגדרות חדשות או לשנות מאפיינים – אבל חשוב לזכור שצריך לפתור את כל הערכים בזמן ההגדרה, ולכן הם לא יכולים להסתמך על קלט חיצוני. אחרי שההפעלה של קריאת החזרה (callback) הזו מסתיימת, אובייקטי ה-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, שאפשר לקבל באמצעות השיטה 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() })
}

הוספת מקורות שנוצרו לגרסה היציבה

הפלאגין יכול לתרום כמה סוגים של מקורות שנוצרו, כמו:

הרשימה המלאה של המקורות שאפשר להוסיף מפורטת במאמר Sources API.

קטע הקוד הזה מראה איך להוסיף תיקיית מקור בהתאמה אישית בשם ${variant.name} לקבוצת מקורות ה-Java באמצעות הפונקציה addStaticSourceDirectory(). לאחר מכן, כלי הפיתוח של 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.

גישה לפריטי מידע שנוצרים בתהליך פיתוח (Artifact) ושינוי שלהם

בנוסף לאפשרות לשנות מאפיינים פשוטים באובייקטים מסוג Variant, AGP מכיל גם מנגנון הרחבה שמאפשר לקרוא או לשנות ארטיפקטים ביניים וסופיים שנוצרו במהלך ה-build. לדוגמה, אפשר לקרוא את תוכן הקובץ AndroidManifest.xml הממוזג הסופי ב-Task בהתאמה אישית כדי לנתח אותו, או להחליף את התוכן שלו לגמרי בתוכן של קובץ מניפסט שנוצר על ידי ה-Task בהתאמה אישית.

רשימת הארטיפקטים הנתמכים נמצאת במסמכי העזרה של הכיתה Artifact. לכל סוג של ארטיפקט יש מאפיינים מסוימים שחשוב לדעת:

עוצמה (cardinality)

העוצמה (cardinality) של Artifact מייצגת את מספר המופעים של FileSystemLocation, או את מספר הקבצים או הספריות מסוג הארטיפקט. כדי לקבל מידע על הכארדינליות של פריט מידע שנוצר בתהליך פיתוח, בודקים את סיווג ההורה שלו: פריטי מידע שנוצרו בתהליך פיתוח עם 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 עם הגרסה הסופית של הארטיפקט (אחרי שכל הפעולות עליו מסתיימות).

אפשר להשתמש בכמה יישומי פלאגין כדי להוסיף לצינור עיבוד הנתונים מספר בלתי מוגבל של פעולות על ארטיפקטים מהקריאה החוזרת (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. לכן צריך להשתמש בשיטה wiredWithFiles, שמקבלת הפניה יחידה מסוג RegularFileProperty לקלט והפניה יחידה מסוג RegularFileProperty לפלט. יש שיטות wiredWith* אחרות בכיתה TaskBasedOperation שיתאימו לשילובים אחרים של עוצמת הקבוצה (cardinality) של Artifact וסוגים של FileSystemLocation.

למידע נוסף על הרחבת AGP, מומלץ לקרוא את הקטעים הבאים במדריך של מערכת ה-build של Gradle: