כתיבת יישומי פלאגין של 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.

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

ספקים גם נושאים מידע על יחסי התלות בין המשימות. כשיוצרים 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 רגילות.

שלב הביצוע

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

Variant API,‏ Artifacts ו-Tasks

Variant API הוא מנגנון הרחבה בפלאגין Android Gradle שמאפשר לבצע פעולות שונות על האפשרויות השונות, שבדרך כלל מוגדרות באמצעות DSL בקובצי התצורה של ה-build, שמשפיעות על ה-build של Android. וריאנט 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 ובארטיפקטים שנוצרים.

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

  6. onVariants(): בקריאה החוזרת (callback) הזה מקבלים גישה לאובייקטים 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, כדי ללמוד איך ליצור פלאגין בפרויקט. דוגמה לרישום הקריאות החוזרות מקוד הפלאגין:

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() })
}

הוספת מקורות שנוצרו ל-build

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

בממשק 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)

העוצמה של 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) שהם מחלקות משנה של 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: