Pisanie wtyczek Gradle

Wtyczka Androida do obsługi Gradle (AGP) to oficjalny system kompilacji aplikacji na Androida. Obejmuje ona obsługę kompilowania wielu różnych typów źródeł i łączenia 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ą wtyczkom kontrolowanie danych wejściowych kompilacji i rozszerzanie jej funkcjonalności za pomocą nowych kroków, które można zintegrować ze standardowymi zadaniami kompilacji. Poprzednie wersje AGP nie miały oficjalnych interfejsów API wyraźnie oddzielonych od implementacji wewnętrznych. Od wersji 7.0 AGP ma 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ętrzne: nie są przeznaczone do użytku publicznego.
  • W fazie rozwoju: dostępne do użytku publicznego, ale nie są ostateczne, co oznacza, że w wersji finalnej mogą nie być wstecznie kompatybilne.
  • Publiczna: dostępna do użytku publicznego i stabilna.
  • Wycofane: nie są już obsługiwane i zostały zastąpione nowymi interfejsami API.

Zasady wycofywania

AGP rozwija się dzięki wycofywaniu starych interfejsów API i zastępowaniu ich nowymi, stabilnymi interfejsami API oraz nowym językiem DSL (Domain Specific Language). Ten proces będzie obejmować kilka wersji AGP. Więcej informacji znajdziesz w harmonogramie migracji interfejsu AGP API/DSL.

Gdy interfejsy API AGP zostaną wycofane (w ramach tej migracji lub w innych okolicznościach), nadal będą dostępne w bieżącej wersji głównej, ale będą generować ostrzeżenia. Wycofane interfejsy API zostaną całkowicie usunięte z AGP w kolejnej wersji głównej. Jeśli na przykład interfejs API zostanie wycofany w AGP 7.0, będzie dostępny w tej wersji i będzie generować ostrzeżenia. Ten interfejs API nie będzie już dostępny w AGP 8.0.

Przykłady nowych interfejsów API używanych w przypadku typowych dostosowań kompilacji znajdziesz w przepisach na wtyczkę Androida do Gradle. Zawierają przykłady typowych dostosowań kompilacji. Więcej informacji o nowych interfejsach API znajdziesz w naszej dokumentacji referencyjnej.

Podstawy kompilacji Gradle

Ten przewodnik nie obejmuje całego systemu kompilacji Gradle. Zawiera jednak minimalny zestaw pojęć niezbędnych do integracji z naszymi interfejsami API, a także linki do głównej dokumentacji Gradle, w której znajdziesz więcej informacji.

Zakładamy, że masz podstawową wiedzę o tym, jak działa Gradle, w tym jak konfigurować projekty, edytować pliki kompilacji, stosować wtyczki i uruchamiać zadania. Aby poznać podstawy Gradle w kontekście AGP, zapoznaj się z artykułem Konfigurowanie kompilacji. Ogólne informacje o dostosowywaniu wtyczek Gradle znajdziesz w artykule Developing Custom Gradle Plugins (Tworzenie niestandardowych wtyczek Gradle).

Słowniczek typów leniwych Gradle

Gradle oferuje kilka typów, które działają „leniwie” lub pomagają odłożyć złożone obliczenia lub Task tworzenie na późniejsze etapy kompilacji. Te typy są podstawą wielu interfejsów API Gradle i AGP. Poniżej znajdziesz listę głównych typów Gradle, które biorą udział w leniwej egzekucji, oraz ich kluczowe metody.

Provider<T>
Zwraca wartość typu T (gdzie „T” oznacza dowolny typ), którą można odczytać w fazie wykonywania za pomocą get() lub przekształcić w nową wartość typu Provider<S> (gdzie „S” oznacza inny typ) za pomocą metod map(), flatMap()zip(). Pamiętaj, że metody get() nie należy wywoływać podczas fazy konfiguracji.
  • map(): przyjmuje funkcję lambda i zwraca Provider typu S, Provider<S>. Argument lambda funkcji map() przyjmuje wartość T i zwraca wartość S. Funkcja lambda nie jest wykonywana od razu, ale jej wykonanie jest odroczone do momentu, w którym na wynikowym obiekcie Provider<S> zostanie wywołana funkcja get(), co sprawia, że cały łańcuch jest leniwy.
  • flatMap(): akceptuje też funkcję LAMBDA i tworzy Provider<S>, ale funkcja LAMBDA przyjmuje wartość T i tworzy Provider<S> (zamiast tworzyć wartość S bezpośrednio). Użyj funkcji flatMap(), gdy nie można określić wartości S w momencie konfiguracji i możesz uzyskać tylko Provider<S>. W praktyce, jeśli użyjesz map() i uzyskasz typ wyniku Provider<Provider<S>>, prawdopodobnie oznacza to, że zamiast tego należało użyć flatMap().
  • zip(): Umożliwia połączenie 2 instancji Provider w celu utworzenia nowej instancji Provider o wartości obliczonej za pomocą funkcji, która łączy wartości z 2 instancji wejściowych Providers.
Property<T>
Implementuje interfejs Provider<T>, więc udostępnia też wartość typu T. W przeciwieństwie do parametru Provider<T>, który jest przeznaczony tylko do odczytu, możesz też ustawić wartość parametru Property<T>. Możesz to zrobić na 2 sposoby:
  • Ustaw wartość typu T bezpośrednio, gdy jest dostępna, bez konieczności odroczonych obliczeń.
  • Ustaw inne Provider<T> jako źródło wartości Property<T>. W tym przypadku wartość T jest materializowana tylko wtedy, gdy wywoływana jest funkcja Property.get().
TaskProvider
Implementuje Provider<Task>. Aby wygenerować TaskProvider, użyj tasks.register(), a nie tasks.create(). Dzięki temu zadania będą tworzone tylko wtedy, gdy będą potrzebne. Możesz użyć flatMap(), aby uzyskać dostęp do danych wyjściowych Task przed utworzeniem Task. Może to być przydatne, jeśli chcesz użyć danych wyjściowych jako danych wejściowych dla innych instancji Task.

Dostawcy i ich metody transformacji są niezbędni do leniwego konfigurowania danych wejściowych i wyjściowych zadań, czyli bez konieczności tworzenia wszystkich zadań z góry i rozwiązywania wartości.

Dostawcy zawierają też informacje o zależnościach między zadaniami. Gdy utworzysz Provider, przekształcając dane wyjściowe Task, ten Task staje się niejawną zależnością Provider i będzie tworzony oraz uruchamiany za każdym razem, gdy zostanie rozwiązana wartość Provider, np. gdy będzie potrzebny innemu Task.

Oto przykład rejestrowania 2 zadań, GitVersionTaskManifestProducerTask, z odroczeniem tworzenia instancji Task do momentu, w którym będą one rzeczywiście potrzebne. Wartość wejściowa ManifestProducerTask jest ustawiana na wartość aProvider uzyskaną z danych wyjściowych funkcji GitVersionTask, więc ManifestProducerTask jest pośrednio zależna 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 zostaną wykonane tylko wtedy, gdy użytkownik wyraźnie o to poprosi. Może to nastąpić w ramach wywołania Gradle, np. jeśli uruchomisz polecenie ./gradlew debugManifestProducer lub jeśli dane wyjściowe polecenia ManifestProducerTask są połączone z innym zadaniem i jego wartość staje się wymagana.

Będziesz pisać niestandardowe zadania, które wykorzystują dane wejściowe lub generują dane wyjściowe, ale AGP nie oferuje publicznego dostępu do własnych zadań. Są one szczegółami implementacji, które mogą się zmieniać w kolejnych wersjach. Zamiast tego AGP udostępnia interfejs Variant API i dostęp do wyników swoich zadań lub artefaktów kompilacji, które możesz odczytywać i przekształcać. Więcej informacji znajdziesz w sekcji Interfejs API wariantów, artefakty i zadania w tym dokumencie.

Fazy kompilacji Gradle

Tworzenie projektu jest z natury skomplikowanym i wymagającym zasobów procesem. Istnieją różne funkcje, takie jak unikanie konfiguracji zadań, aktualne sprawdzanie i funkcja buforowania konfiguracji, które pomagają zminimalizować czas poświęcony na powtarzalne lub niepotrzebne obliczenia.

Aby zastosować niektóre z tych optymalizacji, skrypty i wtyczki Gradle muszą przestrzegać ścisłych reguł w każdej z odrębnych faz kompilacji Gradle: inicjowania, konfiguracji i wykonywania. W tym przewodniku skupimy się na fazach konfiguracji i wdrażania. Więcej informacji o wszystkich fazach znajdziesz w przewodniku po cyklu życia kompilacji Gradle.

Faza konfiguracji

W fazie konfiguracji oceniane są skrypty kompilacji wszystkich projektów, które są częścią kompilacji, stosowane są wtyczki i rozwiązywane są zależności kompilacji. W tej fazie należy skonfigurować kompilację za pomocą obiektów DSL i zarejestrować zadania oraz ich dane wejściowe w sposób odroczony.

Faza konfiguracji jest zawsze uruchamiana niezależnie od tego, które zadanie ma zostać wykonane, dlatego szczególnie ważne jest, aby była jak najprostsza i aby żadne obliczenia nie zależały od danych wejściowych innych niż same skrypty kompilacji. Oznacza to, że nie należy wykonywać programów zewnętrznych ani odczytywać danych z sieci ani przeprowadzać długich obliczeń, które można odłożyć do fazy wykonania jako odpowiednie instancje Task.

Faza wykonania

W fazie wykonywania realizowane są żądane zadania i ich zadania zależne. W szczególności wykonywane są metody klasy Task oznaczone symbolem @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 problemów z leniwymi dostawcami w ten sposób rozpoczyna sekwencję wywołań map() lub flatMap(), które są zgodne z informacjami o zależnościach zadań zawartymi w dostawcy. Zadania są wykonywane z opóźnieniem, aby uzyskać wymagane wartości.

Interfejs API wariantów, artefakty i zadania

Interfejs Variant API to mechanizm rozszerzający wtyczkę Androida do Gradle, który umożliwia manipulowanie różnymi opcjami, zwykle ustawianymi za pomocą DSL w plikach konfiguracji kompilacji, które mają wpływ na kompilację Androida. Interfejs Variant API umożliwia też dostęp do artefaktów pośrednich i końcowych utworzonych przez kompilację, takich jak pliki klas, scalony manifest czy pliki APK/AAB.

Proces kompilacji Androida i punkty rozszerzeń

Podczas interakcji z AGP używaj specjalnie utworzonych punktów rozszerzeń zamiast rejestrować typowe wywołania zwrotne cyklu życia Gradle (np. afterEvaluate()) lub konfigurować jawne zależności Task. Zadania utworzone przez AGP są traktowane jako szczegóły implementacji i nie są udostępniane jako publiczny interfejs API. Musisz unikać prób uzyskania instancji obiektów Task lub zgadywania nazw Task i bezpośredniego dodawania wywołań zwrotnych lub zależności do tych obiektów Task.

Aby utworzyć i wykonać instancje Task, które z kolei generują artefakty kompilacji, AGP wykonuje te czynności: Główne etapy tworzenia obiektuVariant są wykonywane po wywołaniach zwrotnych, które umożliwiają wprowadzanie zmian w niektórych obiektach utworzonych w ramach kompilacji. Pamiętaj, że wszystkie wywołania zwrotne występują w fazie konfiguracji (opisanej na tej stronie) i muszą działać szybko, odkładając wszelkie skomplikowane zadania na odpowiednie instancje Task w fazie wykonania.

  1. Parsowanie DSL: w tym momencie oceniane są skrypty kompilacji, a także tworzone i ustawiane są różne właściwości obiektów DSL Androida z bloku android. Podczas tej fazy rejestrowane są też wywołania zwrotne interfejsu Variant API opisane w kolejnych sekcjach.
  2. finalizeDsl(): wywołanie zwrotne, które umożliwia zmianę obiektów DSL przed ich zablokowaniem na potrzeby tworzenia komponentów (wariantów). Obiekty VariantBuilder są tworzone na podstawie danych zawartych w obiektach DSL.

  3. Blokowanie DSL: DSL jest teraz zablokowany i nie można już wprowadzać zmian.

  4. beforeVariants(): ta funkcja zwrotna może wpływać na to, które komponenty są tworzone, i na niektóre ich właściwości za pomocą funkcji VariantBuilder. Nadal umożliwia modyfikowanie procesu kompilacji i wytwarzanych artefaktów.

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

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

  7. Blokowanie wariantów: obiekty wariantów są teraz zablokowane i nie można już wprowadzać w nich zmian.

  8. Utworzone zadania: obiekty Variant i ich wartości Property są używane do tworzenia instancji Task niezbędnych do przeprowadzenia kompilacji.

AGP wprowadza AndroidComponentsExtension, który umożliwia rejestrowanie wywołań zwrotnych dla finalizeDsl(), beforeVariants()onVariants(). Rozszerzenie jest dostępne w skryptach kompilacji w 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ą DSL bloku androida i przenosić niestandardową logikę imperatywną do buildSrc lub zewnętrznych wtyczek. Możesz też zapoznać się z buildSrcprzykładami w naszym repozytorium GitHub z przepisami Gradle, aby dowiedzieć się, jak utworzyć wtyczkę w projekcie. 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ę dostępnym wywołaniom zwrotnym i rodzajom przypadków użycia, które wtyczka może obsługiwać w każdym z nich:

finalizeDsl(callback: (DslExtensionT) -> Unit)

W tym wywołaniu zwrotnym możesz uzyskać dostęp do obiektów DSL utworzonych przez przeanalizowanie informacji z bloku android w plikach kompilacji i je zmodyfikować. Te obiekty DSL będą używane do inicjowania i konfigurowania wariantów w późniejszych fazach kompilacji. Możesz na przykład programowo tworzyć nowe konfiguracje lub zastępować właściwości, ale pamiętaj, że wszystkie wartości muszą być rozwiązywane w momencie konfiguracji, więc nie mogą zależeć od żadnych danych wejściowych z zewnątrz. Po zakończeniu wykonywania tego wywołania zwrotnego obiekty DSL nie są już przydatne i nie należy już przechowywać do nich odwołań ani modyfikować ich wartości.

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 tworzenia uzyskujesz dostęp do obiektów VariantBuilder, które określają warianty, jakie zostaną utworzone, oraz ich właściwości. Możesz na przykład programowo 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 finalizeDsl() wszystkie podane wartości muszą być rozpoznawane w momencie konfiguracji i nie mogą zależeć od danych wejściowych z zewnątrz. Po zakończeniu wykonywania wywołania zwrotnego beforeVariants() nie można modyfikować obiektów VariantBuilder.

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

Wywołanie zwrotne beforeVariants() opcjonalnie przyjmuje wartość VariantSelector, którą możesz uzyskać za pomocą metody selector() w obiekcie androidComponentsExtension. Możesz go użyć do filtrowania komponentów uczestniczących w wywołaniu zwrotnym 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 AGP, są już określone, więc nie można ich już wyłączyć. Możesz jednak zmodyfikować niektóre wartości używane w przypadku zadań, ustawiając je dla atrybutów Property w obiektach Variant. Wartości Property zostaną rozwiązane dopiero po wykonaniu zadań AGP, więc możesz je bezpiecznie połączyć z dostawcami z własnych zadań niestandardowych, które wykonają wszelkie wymagane obliczenia, w tym odczyt z zewnętrznych danych wejściowych, takich jak pliki lub 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() })
}

Wnoszenie wygenerowanych źródeł do kompilacji

Wtyczka może generować kilka rodzajów źródeł, takich jak:

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

Ten fragment kodu pokazuje, jak dodać do zestawu źródeł Java niestandardowy folder źródłowy o nazwie ${variant.name} za pomocą funkcji addStaticSourceDirectory(). Następnie łańcuch narzędzi Androida przetwarza ten folder.

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

Więcej informacji znajdziesz w przepisie addJavaSource.

Ten fragment kodu pokazuje, jak dodać katalog z zasobami Androida wygenerowanymi na podstawie niestandardowego zadania do zestawu źródeł res. 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 modyfikowania prostych właściwości obiektów Variant AGP zawiera też mechanizm rozszerzeń, który umożliwia odczytywanie lub przekształcanie artefaktów pośrednich i końcowych wygenerowanych podczas kompilacji. Możesz na przykład odczytać zawartość końcowego, scalonego pliku AndroidManifest.xml w niestandardowym Task, aby ją przeanalizować, lub całkowicie zastąpić ją zawartością pliku manifestu wygenerowanego przez niestandardowy Task.

Listę artefaktów, które są obecnie obsługiwane, znajdziesz w dokumentacji referencyjnej klasy Artifact. Każdy typ artefaktu ma określone właściwości, które warto znać:

Moc zbioru

Moc zbioru Artifact to liczba jego FileSystemLocation instancji, czyli liczba plików lub katalogów danego typu artefaktu. Informacje o kardynalności artefaktu możesz uzyskać, sprawdzając jego klasę nadrzędną: artefakty z pojedynczym FileSystemLocation będą podklasą Artifact.Single, a artefakty z wieloma instancjami FileSystemLocation będą podklasą Artifact.Multiple.

FileSystemLocation typ

Możesz sprawdzić, czy Artifact reprezentuje pliki czy katalogi, sprawdzając jego sparametryzowany typ FileSystemLocation, który może być RegularFile lub 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 danych wejściowych dla Task, który wykonuje na nim dowolne przekształcenia i zwraca nową wersję Artifact.
  • Appendable: dotyczy tylko artefaktów, które są podklasami Artifact.Multiple. Oznacza to, że do Artifact można dodawać elementy, czyli niestandardowy Task może tworzyć nowe instancje tego typu Artifact, które zostaną dodane do istniejącej listy.
  • Replaceable: dotyczy tylko artefaktów, które są podklasami Artifact.Single. Wymienny Artifact można zastąpić zupełnie nową instancją, która jest wynikiem działania Task.

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

Wiele wtyczek może dodawać do potoku dowolną liczbę operacji na artefaktach z wywołania zwrotnego onVariants(), a AGP zadba o ich prawidłowe połączenie, 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 zmieni dane wyjściowe przez dodanie, zastąpienie lub przekształcenie ich, następna operacja zobaczy zaktualizowaną wersję tych artefaktów jako dane wejściowe i tak dalej.

Punktem wejścia do rejestrowania operacji jest 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ć niestandardowy element TaskProvider, aby uzyskać obiekt TaskBasedOperation (1) i użyć go do połączenia jego wejść i wyjść za pomocą jednej z metod wiredWith* (2).

Metoda, którą musisz wybrać, zależy od liczności i FileSystemLocationtypu zaimplementowanego przez Artifact, który chcesz przekształcić.

Na koniec przekazujesz Artifact do metody reprezentującej wybraną operację na obiekcie *OperationRequest, który otrzymujesz w zamian, 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 to SingleArtifact, a RegularFile. Dlatego musimy użyć metody wiredWithFiles, która przyjmuje 1 RegularFileProperty jako dane wejściowe i 1 RegularFileProperty jako dane wyjściowe. Istnieją inne wiredWith* metody w klasie TaskBasedOperation, które będą działać w przypadku innych kombinacji Artifact liczności i FileSystemLocation typów.

Więcej informacji o rozszerzaniu AGP znajdziesz w tych sekcjach podręcznika systemu kompilacji Gradle: