Pisanie wtyczek Gradle

Wtyczka Androida do obsługi Gradle (AGP) to oficjalny system kompilacji aplikacji na Androida. Obejmuje ona kompilowanie wielu różnych typów źródeł i łączenie ich w aplikację, którą można uruchomić na fizycznym urządzeniu z Androidem lub w emulatorze.

AGP zawiera punkty rozszerzeń, które umożliwiają kontrolowanie danych wejściowych kompilacji i rozszerzanie funkcjonalności za pomocą nowych kroków, które można zintegrować ze standardowymi zadaniami kompilacji. W poprzednich wersjach AGP oficjalne interfejsy API nie były wyraźnie oddzielone od implementacji wewnętrznych. Od wersji 7.0 AGP zawiera zestaw oficjalnych, stabilnych interfejsów API, na których możesz polegać.

Cykl życia interfejsu AGP API

AGP stosuje cykl życia funkcji Gradle, aby określać stan interfejsów API:

  • Wewnętrzny: nie przeznaczony do użytku publicznego.
  • W fazie testów: dostępne do publicznego użytku, ale nie są wersjami ostatecznymi, co oznacza, że w ostatecznej wersji mogą nie być zgodne z wcześniejszymi wersjami.
  • Publiczna: stabilna wersja publiczna.
  • Wycofany: nie jest już obsługiwany i został zastąpiony nowym interfejsem API.

Zasady wycofywania

AGP ewoluuje wraz z wycofaniem starych interfejsów API i zastąpieniem ich nowymi, stabilnymi interfejsami API oraz nowym językiem do zastosowań w domenie (DSL). Ta ewolucja obejmie wiele wersji interfejsu AGP. Więcej informacji znajdziesz w harmonogramie migracji interfejsów API i interfejsów DSL AGP.

Gdy wycofamy interfejsy AGP (np. w ramach tej migracji), nadal będą one dostępne w obecnej głównej wersji, ale będą generować ostrzeżenia. Wycofane interfejsy API zostaną całkowicie usunięte z Google Analytics 4 w koleistycznej dużej aktualizacji. Jeśli na przykład interfejs API został wycofany w wersji AGP 7.0, będzie on dostępny w tej wersji i wygeneruje ostrzeżenia. Ten interfejs API nie będzie już dostępny w AGP 8.0.

Przykłady nowych interfejsów API używanych w ramach typowych dostosowań kompilacji znajdziesz w przepisach wtyczki Gradle na Androida. Znajdziesz w nich przykłady typowych dostosowań kompilacji. Więcej informacji o nowych interfejsach API znajdziesz w naszej dokumentacji referencyjnej.

Podstawy kompilacji w Gradle

Ten przewodnik nie obejmuje całego systemu kompilacji Gradle. Zawiera on jednak minimalny zestaw pojęć, który pomoże Ci w integracji z naszych interfejsami API, oraz linki do głównej dokumentacji Gradle, w której znajdziesz więcej informacji.

Zakładamy, że znasz podstawy działania Gradle, w tym konfigurowanie projektów, edytowanie plików kompilacji, stosowanie wtyczek i uruchamianie zadań. Aby dowiedzieć się więcej o podstawach Gradle w związku z AGP, przeczytaj artykuł Konfigurowanie procesu kompilacji. Informacje o ogólnej strukturze dostosowywania wtyczek Gradle znajdziesz w artykule Tworzenie niestandardowych wtyczek Gradle.

Słownik typów nieaktywnych w Gradle

Gradle udostępnia kilka typów, które działają „leniwie” lub pomagają odłożyć na późniejsze etapy kompilacji intensywne obliczenia lub tworzenie Task. Te typy są podstawą wielu interfejsów API Gradle i AGP. Poniższa lista zawiera główne typy Gradle używane w opóźnionym wykonywaniu oraz ich kluczowe metody.

Provider<T>
Dostarcza wartość typu T (gdzie „T” oznacza dowolny typ), którą można odczytać podczas fazy wykonywania za pomocą metody get() lub przekształcić w nową wartość Provider<S> (gdzie „S” oznacza inny typ) za pomocą metod map(), flatMap() i zip(). Pamiętaj, że metoda get() nigdy nie powinna być wywoływana na etapie konfiguracji.
  • map(): przyjmuje lambda i tworzy Provider typu S, Provider<S>. Argument lambda funkcji map() przyjmuje wartość T i zwraca wartość S. Funkcja lambda nie jest wykonywana od razu. Jej wykonanie jest odroczone do momentu wywołania funkcji get() w wynikającym z niej obiekcie Provider<S>, dzięki czemu cały łańcuch jest opóźniony.
  • flatMap(): przyjmuje funkcję lambda i zwraca wartość Provider<S>, ale funkcja lambda przyjmuje wartość T i zwraca wartość Provider<S> (zamiast bezpośrednio wartości S). Użyj flatMap(), gdy S nie może zostać określony w czasie konfiguracji i możesz uzyskać tylko Provider<S>. Praktycznie rzecz biorąc, jeśli użyjesz funkcji map(), a w efekcie otrzymasz typ wyniku Provider<Provider<S>>, prawdopodobnie powinieneś użyć funkcji flatMap().
  • zip(): umożliwia połączenie 2 wystąpienia funkcji Provider w celu utworzenia nowej wartości Provider, obliczonej za pomocą funkcji, która łączy wartości z 2 wejść Providers.
Property<T>
Wdraża Provider<T>, więc również udostępnia wartość typu T. W przeciwieństwie do parametru Provider<T>, który jest przeznaczony tylko do odczytu, parametr Property<T> może też mieć ustawioną wartość. Możesz to zrobić na 2 sposoby:
  • Ustaw wartość typu T bezpośrednio, gdy jest dostępna, bez potrzeby odroczonego przetwarzania.
  • Jako źródła wartości Property<T> użyj innego parametru Provider<T>. W tym przypadku wartość T jest materializowana tylko wtedy, gdy wywołana jest funkcja Property.get().
TaskProvider
Wdroż Provider<Task>. Aby wygenerować TaskProvider, użyj elementu tasks.register(), a nie tasks.create(), aby mieć pewność, że zadania są tworzone tylko wtedy, gdy są potrzebne. Za pomocą funkcji flatMap() możesz uzyskać dostęp do danych wyjściowych funkcji Task przed jej utworzeniem. Może to być przydatne, jeśli chcesz użyć tych danych jako danych wejściowych dla innych instancji funkcji Task.Task

Dostawcy i ich metody transformacji są niezbędne do konfigurowania danych wejściowych i wyjściowych zadań w sposób leniwy, czyli bez konieczności tworzenia wszystkich zadań z wyprzedzeniem i rozwiązywania wartości.

Dostawcy przekazują też informacje o zależnościach zadań. Gdy utworzysz funkcję Provider, przekształcając dane wyjściowe funkcji Task, ta pierwsza stanie się jej domyślną zależnością i będzie tworzona oraz uruchamiana zawsze, gdy wartość funkcji Provider zostanie rozwiązana, np. gdy będzie to wymagane przez inną funkcję Task.ProviderTask

Oto przykład rejestrowania 2 zadań, GitVersionTaskManifestProducerTask, przy czym tworzenie instancji Task jest odkładane do momentu, gdy są one rzeczywiście potrzebne. Wartość wejściowa ManifestProducerTask jest ustawiana na Provider uzyskaną z wyjścia funkcji GitVersionTask, więc ManifestProducerTask pośrednio zależy od 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))
    }

Te 2 zadania będą wykonywane tylko wtedy, gdy zostanie wysłane odpowiednie żądanie. Może się to zdarzyć w ramach wywołania Gradle, na przykład gdy uruchomisz ./gradlew debugManifestProducer, lub gdy dane wyjściowe ManifestProducerTask są połączone z jakimś innym zadaniem i ich wartość staje się wymagana.

Podczas gdy będziesz pisać zadania niestandardowe, które pobierają dane wejściowe lub generują dane wyjściowe, usługa AGPC nie udostępnia publicznie własnych zadań. Są to szczegóły implementacji, które mogą się zmieniać w kolejnych wersjach. Zamiast tego AGP oferuje interfejs Variant API i dostęp do wyników jego zadań, czyli elementów budujących, które możesz odczytać i przekształcić. Więcej informacji znajdziesz w tym dokumencie w sekcji Variant API, Artifacts, and Tasks (Interfejs API wariantów, artefakty i zadania).

Etapy kompilacji Gradle

Tworzenie projektu to z zasady skomplikowany proces, który wymaga wielu zasobów. Dostępne są różne funkcje, takie jak unikanie konfiguracji zadań, aktualne kontrole i funkcja buforowania konfiguracji, które pomagają zminimalizować czas poświęcany na powtarzalne lub niepotrzebne obliczenia.

Aby zastosować niektóre z tych optymalizacji, skrypty i wtyczki Gradle muszą przestrzegać ścisłych reguł na każdym z etapów kompilacji Gradle: inicjalizacji, konfiguracji i wykonania. W tym przewodniku skupimy się na fazach konfiguracji i wykonania. Więcej informacji o wszystkich fazach znajdziesz w przewodniku po cyklu życia kompilacji Gradle.

Etap konfiguracji

W trakcie konfiguracji oceniane są skrypty kompilacji wszystkich projektów wchodzących w skład kompilacji, stosowane są wtyczki, a zależności kompilacji są rozwiązywane. Ta faza służy do konfigurowania kompilacji za pomocą obiektów DSL oraz do leniwego rejestrowania zadań i ich danych wejściowych.

Faza konfiguracji jest zawsze wykonywana niezależnie od tego, jakie zadanie zostało uruchomione, dlatego ważne jest, aby była jak najbardziej zwięzła i niezależna od danych wejściowych innych niż skrypty kompilacji. Oznacza to, że nie należy uruchamiać programów zewnętrznych ani odczytywać danych z sieci ani wykonywać długich obliczeń, które można odłożyć do fazy wykonywania jako odpowiednie instancje Task.

Faza wykonania

W etapie wykonania są wykonywane żądane zadania i ich zadania zależne. W szczególności wykonywane są metody klasy Task oznaczone jako @TaskAction. Podczas wykonywania zadania możesz odczytywać dane wejściowe (np. pliki) i rozwiązywać leniwych dostawców, wywołując funkcję Provider<T>.get(). Rozwiązywanie dostawców typu lazy w ten sposób uruchamia sekwencję wywołań map() lub flatMap(), które podążają za informacjami o zależnościach zadań zawartymi w dostawcy. Zadania są wykonywane cyklicznie, aby wygenerować wymagane wartości.

Interfejs API wariantów, artefakty i zadania

Interfejs Variant API to mechanizm rozszerzeń w pliku Android Gradle, który umożliwia manipulowanie różnymi opcjami, zwykle ustawianymi za pomocą języka DSL w plikach konfiguracji kompilacji, które wpływają na kompilację Androida. Interfejs Variant API zapewnia też dostęp do pośrednich i ostatecznych artefaktów utworzonych przez kompilację, takich jak pliki klasy, scalony plik manifestu lub pliki APK/AAB.

Proces kompilacji aplikacji na Androida i punkty rozszerzenia

Podczas interakcji z AGP używaj specjalnie utworzonych punktów rozszerzenia zamiast rejestrować typowe wywołania cyklu życia Gradle (takie jak afterEvaluate()) lub konfigurować jawne zależności Task. Zadania tworzone przez AGP są uważane za szczegóły implementacji i nie są udostępniane jako publiczny interfejs API. Nie próbuj uzyskiwać instancji obiektów Task ani zgadywać ich nazw, a także nie dodawaj bezpośrednio do tych obiektów wywołań zwrotnych ani zależności.TaskTask

AGP wykonuje te czynności, aby utworzyć i wykonać instancje Task, które z kolei generują artefakty kompilacji. Główne kroki związane z tworzeniem obiektu Variant są poprzedzone wywołaniami zwrotnymi, które umożliwiają wprowadzanie zmian w określonych obiektach utworzonych w ramach kompilacji. Pamiętaj, że wszystkie wywołania zwrotne są wykonywane w fazie konfiguracji (opisanej na tej stronie) i muszą być szybkie, a każda skomplikowana operacja musi zostać odroczona do właściwych instancji Task w fazie wykonania.

  1. Analizowanie DSL: w tym momencie są oceniane skrypty kompilacji oraz tworzone i ustawiane są różne właściwości obiektów DSL Androida z bloku android. W tej fazie są też rejestrowane wywołania zwrotne interfejsu Variant API opisane w następnych sekcjach.
  2. finalizeDsl(): Funkcja wywołania zwrotnego, która umożliwia zmianę obiektów DSL, zanim zostaną zablokowane na potrzeby tworzenia komponentu (wariantu). Obiekty VariantBuilder są tworzone na podstawie danych zawartych w obiektach DSL.

  3. Blokowanie DSL: usługa DSL jest teraz zablokowana i nie można już wprowadzać zmian.

  4. beforeVariants(): ten wywołanie zwrotne może wpływać na to, które komponenty są tworzone, a także na niektóre ich właściwości za pomocą VariantBuilder. Nadal umożliwia wprowadzanie zmian w procesie kompilacji i w wygenerowanych artefaktach.

  5. Tworzenie wariantów: lista komponentów i elementów, które zostaną utworzone, jest już sfinalizowana i nie można jej zmienić.

  6. onVariants(): W tym wywołaniu zwrotnym uzyskujesz dostęp do utworzonych obiektów Variant i możesz dla zawartych przez nie wartości Property ustawić wartości lub dostawców, aby były obliczane z opóźnieniem.

  7. Blokowanie wariantów: obiekty wariantów są teraz zablokowane i nie można ich już zmieniać.

  8. Utworzone zadania: obiekty Variant i ich wartości Property są używane do tworzenia instancji Task, które są niezbędne do wykonania kompilacji.

AGP wprowadza parametr AndroidComponentsExtension, który umożliwia rejestrowanie wywołań zwrotnych dla zdarzeń finalizeDsl(), beforeVariants()onVariants(). Rozszerzenie jest dostępne w skryptach kompilacji za pomocą bloku androidComponents:

// This is used only for configuring the Android build through DSL.
android { ... }

// The androidComponents block is separate from the DSL.
androidComponents {
   finalizeDsl { extension ->
      ...
   }
}

Zalecamy jednak, aby skrypty kompilacji służyły tylko do deklaratywnej konfiguracji za pomocą języka DSL bloku Androida, a dowolną niestandardową logikę imperatywną przenieść do buildSrc lub zewnętrznych wtyczek. Aby dowiedzieć się, jak utworzyć w projekcie wtyczkę, możesz też przejrzeć przykłady buildSrc w repozytorium z przepisami Gradle w GitHub. Oto przykład rejestrowania wywołań zwrotnych z kodu wtyczki:

abstract class ExamplePlugin: Plugin<Project> {

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

Przyjrzyjmy się bliżej dostępnym funkcjom zwracania wartości i przypadkom użycia, które może obsługiwać Twój wtyczka:

finalizeDsl(callback: (DslExtensionT) -> Unit)

W tym wywołaniu zwrotnym możesz uzyskiwać dostęp do obiektów DSL i je modyfikować. Zostały one utworzone przez zanalizowanie informacji z bloku android w plikach kompilacji. Te obiekty DSL będą używane do inicjowania i konfigurowania wariantów w kolejnych fazach kompilacji. Możesz na przykład tworzyć nowe konfiguracje za pomocą programów lub zastępować właściwości. Pamiętaj jednak, że wszystkie wartości muszą być rozwiązywane w momencie konfiguracji, więc nie mogą zależeć od żadnych danych zewnętrznych. Po zakończeniu wykonywania tej funkcji wywołania zwrotnego obiekty DSL nie są już przydatne i nie należy już ich trzymać ani modyfikować.

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

Na tym etapie kompilacji uzyskujesz dostęp do obiektów VariantBuilder, które określają tworzone warianty i ich właściwości. Możesz na przykład za pomocą kodu wyłączyć określone warianty lub ich testy albo zmienić wartość właściwości (np. minSdk) tylko w przypadku wybranego wariantu. Podobnie jak w przypadku parametru finalizeDsl() wszystkie podane przez Ciebie wartości muszą zostać rozwiązane w momencie konfiguracji i nie mogą zależeć od zewnętrznych danych wejściowych. Po zakończeniu wykonywania wywołania zwrotnego beforeVariants() obiektów VariantBuilder nie można modyfikować.

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

Opcjonalnie wywołanie zwrotne beforeVariants() może przyjmować parametr VariantSelector, który możesz uzyskać za pomocą metody selector() w obiekcie androidComponentsExtension. Możesz go użyć do filtrowania komponentów biorących udział w wywoływaniu funkcji zwrotnej na podstawie ich nazwy, typu kompilacji lub wersji produktu.

androidComponents {
    beforeVariants(selector().withName("adfree")) { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

onVariants()

W momencie wywołania funkcji onVariants() wszystkie artefakty, które zostaną utworzone przez AGPL, są już określone, więc nie można ich wyłączyć. Możesz jednak zmodyfikować niektóre wartości używane do zadań, ustawiając je w atrybutach Property w obiektach Variant. Wartości Property będą przetwarzane tylko podczas wykonywania zadań AGP, więc możesz je bezpiecznie podłączyć do dostawców z własnych niestandardowych zadań, które wykonają wszystkie niezbędne obliczenia, w tym odczyt z zewnętrznych danych wejściowych, takich jak pliki czy sieć.

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

Dodawanie wygenerowanych źródeł do kompilacji

Twój wtyczek może udostępniać kilka typów generowanych źródeł, takich jak:

Pełną listę źródeł, które możesz dodać, znajdziesz w interfejsie Sources API.

Ten fragment kodu pokazuje, jak za pomocą funkcji addStaticSourceDirectory() dodać do zbioru źródeł Javy niestandardowy folder ${variant.name}. Następnie narzędzia Androida przetwarzają ten folder.

onVariants { variant ->
    variant.sources.java?.let { java ->
        java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
    }
}

Aby dowiedzieć się więcej, zapoznaj się z przepisem addJavaSource.

Ten fragment kodu pokazuje, jak dodać do zestawu źródeł res katalog z zasobami Androida wygenerowanymi na podstawie niestandardowego zadania. W przypadku innych typów źródeł proces jest podobny.

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.
      ...
   }
}

Więcej informacji znajdziesz w przepisie addCustomAsset.

Dostęp do artefaktów i ich modyfikowanie

Oprócz możliwości modyfikowania prostych właściwości obiektów Variant AGP zawiera też mechanizm rozszerzeń, który umożliwia odczytywanie i przekształcanie artefaktów pośrednich i ostatecznych wygenerowanych podczas kompilacji. Możesz na przykład odczytać zawartość końcowego, scalonego pliku AndroidManifest.xml w niestandardowym pliku Task, aby go przeanalizować, lub całkowicie zastąpić jego zawartość zawartością pliku manifestu wygenerowanego przez niestandardowy plik Task.

Listę elementów obecnie obsługiwanych znajdziesz w dokumentacji referencyjnej klasy Artifact. Każdy typ artefaktu ma określone właściwości, które warto poznać:

Moc zbioru

Moc zbioru Artifact reprezentuje liczbę wystąpień FileSystemLocation lub liczbę plików albo katalogów typu artefaktu. Informacje o kardinalności artefaktu możesz uzyskać, sprawdzając jego klasę nadrzędną: artefakty z jednym FileSystemLocation będą podklasą Artifact.Single, a artefakty z wieloma instancjami FileSystemLocation będą podklasą Artifact.Multiple.

FileSystemLocation typ

Aby sprawdzić, czy Artifact reprezentuje pliki czy katalogi, sprawdź parametryzowany typ FileSystemLocation, który może być albo RegularFile, albo Directory.

Obsługiwane operacje

Każda klasa Artifact może implementować dowolny z tych interfejsów, aby wskazać, które operacje obsługuje:

  • Transformable: umożliwia użycie Artifact jako wejścia dla funkcji Task, która wykonuje na nim dowolne przekształcenia i wyprowadza nową wersję Artifact.
  • Appendable: dotyczy tylko artefaktów, które są podklasami Artifact.Multiple. Oznacza to, że do Artifact można dołączać elementy, czyli niestandardowy element Task może tworzyć nowe wystąpienia tego typu Artifact, które zostaną dodane do istniejącej listy.
  • Replaceable: dotyczy tylko artefaktów, które są podklasami Artifact.Single. Zastępowalny element Artifact może zostać zastąpiony przez zupełnie nowy egzemplarz wygenerowany jako dane wyjściowe elementu Task.

Oprócz 3 operacji modyfikujących artefakt każdy artefakt obsługuje operację get() (lub getAll()), która zwraca Provider z ostateczną wersją artefaktu (po zakończeniu wszystkich operacji).

Wiele wtyczek może dodawać dowolną liczbę operacji na artefaktach do potoku z poziomu wywołania zwrotnego onVariants(), a AGP zadba o to, aby były one odpowiednio połączone, aby wszystkie zadania były wykonywane we właściwym czasie, a artefakty były prawidłowo tworzone i aktualizowane. Oznacza to, że gdy operacja zmienia jakiekolwiek dane wyjściowe przez ich dodanie, zastąpienie lub przekształcenie, następna operacja będzie traktować zaktualizowaną wersję tych artefaktów jako dane wejściowe i tak dalej.

Punkt wejścia do operacji rejestrowania to klasa Artifacts. Ten fragment kodu pokazuje, jak uzyskać dostęp do instancji Artifacts z właściwości obiektu Variant w wywołaniu zwrotnym onVariants().

Następnie możesz przekazać niestandardową zmienną TaskProvider, aby uzyskać obiekt TaskBasedOperation (1), i używać go do łączenia wejść i wyjść za pomocą jednej z metod wiredWith* (2).

Dokładna metoda, którą należy wybrać, zależy od mocy zbioru i typu FileSystemLocation zaimplementowanego przez Artifact, który chcesz przekształcić.

Na koniec przekazujesz typ Artifact do metody reprezentującej wybraną operację na obiekcie *OperationRequest, która zwraca np. toAppendTo(), toTransform() lub 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)
}

W tym przykładzie MERGED_MANIFEST jest SingleArtifact i jest RegularFile. Z tego powodu musimy użyć metody wiredWithFiles, która przyjmuje jako dane wejściowe pojedyncze odwołanie RegularFileProperty, a jako dane wyjściowe pojedynczą wartość RegularFileProperty. W klasie TaskBasedOperation są też inne metody wiredWith*, które działają w przypadku innych kombinacji mocy zbioru Artifact i typów FileSystemLocation.

Aby dowiedzieć się więcej o rozszerzaniu AGP, przeczytaj te sekcje z podręcznika systemu kompilacji Gradle: