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

הפלאגין של 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.

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

יסודות של Gradle build

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

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

שלבי פיתוח 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. וריאנט API מאפשר גם גישה לתוצרי ביניים ופריטי מידע סופיים שנוצרים על ידי ה-build, כמו קובצי מחלקה, המניפסט הממוזג או קובצי APK/AAB.

תהליך ה-build ונקודות התוסף ב-Android

באינטראקציה עם AGP, צריך להשתמש בנקודות תוסף שנוצרו במיוחד במקום לרשום את הקריאות החוזרות הטיפוסיות של מחזור החיים של 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 ->
            ...
        }
    }
}

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

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 אחרי שמסיימים את הקריאה החוזרת של beforeVariants().

androidComponents {
    beforeVariants { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

לחלופין, הקריאה החוזרת של 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

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

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

עוצמה (cardinality)

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