Pisanie wtyczek Gradle

Wtyczka Androida do obsługi Gradle (AGP) to oficjalny system kompilacji aplikacji na Androida. Umożliwia 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 rozszerzenia, które pozwalają wtyczkom kontrolować dane wejściowe kompilacji i rozszerzać jej funkcjonalność dzięki nowym etapom, które można zintegrować ze standardowymi zadaniami kompilacji. Poprzednie wersje AGP nie miały oficjalnych interfejsów API 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 API AGP

Aby wskazać stan interfejsów API, AGP postępuje zgodnie z cyklem życia funkcji Gradle:

  • Wewnętrzny: nieprzeznaczony do użytku publicznego
  • Inkubacja: dostępne do użytku publicznego, ale nie ostateczne. Oznacza to, że mogą nie być zgodne wstecznie w ostatecznej wersji.
  • Publiczny: publiczny i stabilny.
  • Wycofane: usługa nie jest już obsługiwana i zastąpiona nowymi interfejsami API.

Zasady wycofywania

AGP zmienia się wraz z wycofaniem starych interfejsów API i zastąpieniem ich nowymi, stabilnymi interfejsami API oraz nowym językiem w języku specyficznym dla domeny (Domain Specific Language). Ewolucja ta obejmie wiele wersji AGP. Więcej informacji na ten temat znajdziesz na harmonogramie migracji interfejsów AGP API/DSL.

Gdy interfejsy API AGP zostaną wycofane, na potrzeby tej migracji lub w inny sposób będą nadal 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. Na przykład jeśli interfejs API zostanie wycofany w AGP 7.0, będzie dostępny w tej wersji i generuje ostrzeżenia. Ten interfejs API nie będzie dostępny w AGP 8.0.

Aby zobaczyć przykłady nowych interfejsów API używanych w typowych dostosowywaniu kompilacji, zapoznaj się z przepisami na wtyczki Androida do obsługi Gradle. Zawierają przykłady typowych modyfikacji kompilacji. Więcej informacji o nowych interfejsach API znajdziesz w dokumentacji referencyjnej.

Podstawy kompilacji Gradle

Ten przewodnik nie obejmuje całego systemu kompilacji Gradle. Omawiamy w nim jednak minimalny zestaw niezbędnych pojęć, które ułatwiają integrację z naszymi interfejsami API, oraz linki do głównej dokumentacji Gradle, która zawiera więcej informacji.

Zakładamy, że wiesz, jak działa Gradle, w tym jak konfigurować projekty, edytować pliki kompilacji, stosować wtyczki i uruchamiać zadania. Aby poznać podstawy Gradle w odniesieniu do AGP, zapoznaj się z artykułem Konfigurowanie kompilacji. Więcej informacji o ogólnej platformie dostosowywania wtyczek Gradle znajdziesz w artykule o tworzeniu niestandardowych wtyczek Gradle.

Glosariusz leniwego typu Gradle

Gradle udostępnia wiele typów, które działają „leniwie” lub pomagają odłożyć ciężkie obliczenia lub utworzenie Task do późniejszych faz kompilacji. Te typy są podstawą wielu interfejsów API Gradle i AGP. Na liście poniżej znajdziesz główne typy Gradle używane w leniwym wykonywaniu wraz z ich kluczowymi metodami.

Provider<T>
Udostępnia wartość typu T (gdzie „T” oznacza dowolny typ), którą można odczytać w fazie wykonywania za pomocą get() lub przekształcić na nowy obiekt Provider<S> (gdzie „S” oznacza inny typ) za pomocą metod map(), flatMap() i zip(). Pamiętaj, że właściwość get() nie powinna być wywoływana na etapie konfiguracji.
  • map(): akceptuje obiekt lambda i generuje kod Provider typu S (Provider<S>). Argument lambda funkcji map() przyjmuje wartość T i generuje wartość S. Funkcja lambda nie jest wykonywana natychmiast. Zamiast tego jej wykonanie jest odkładane do momentu wywołania funkcji get() na wynikowym Provider<S>, przez co cały łańcuch jest leniwy.
  • flatMap(): akceptuje też funkcję lambda i generuje parametr Provider<S>, ale lambda przyjmuje wartość T i generuje Provider<S> (zamiast bezpośrednio generować wartość S). Użyj FlaMap(), gdy nie można określić S w czasie konfiguracji, a można uzyskać tylko Provider<S>. W praktyce użycie funkcji map() i wynik z wynikiem Provider<Provider<S>> prawdopodobnie oznacza użycie parametru flatMap().
  • zip(): umożliwia połączenie 2 instancji Provider w celu utworzenia nowego parametru Provider z wartością obliczoną za pomocą funkcji łączącej wartości z 2 wejściowych instancji Providers.
Property<T>
Stosuje właściwość Provider<T>, więc podaje również wartość typu T. W przeciwieństwie do właściwości Provider<T>, która jest tylko do odczytu, możesz też ustawić wartość 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 inny Provider<T> jako źródło wartości Property<T>. W tym przypadku wartość T jest realizowana tylko wtedy, gdy wywoływana jest właściwość Property.get().
TaskProvider
Stosuje Provider<Task>. Aby wygenerować TaskProvider, użyj tasks.register(), a nie tasks.create(). Dzięki temu zadania będą tworzone leniwie tylko wtedy, gdy są potrzebne. Dzięki flatMap() możesz uzyskać dostęp do danych wyjściowych Task przed utworzeniem Task, co może być przydatne, jeśli chcesz używać danych wyjściowych jako danych wejściowych do innych instancji Task.

Dostawcy i stosowane przez nich metody przekształcania są niezbędne do konfigurowania danych wejściowych i wyjściowych w zadaniach w leniwy sposób, tzn. bez konieczności tworzenia od razu wszystkich zadań i ustalania ich wartości.

Dostawcy przekazują też informacje o zależnościach zadań. Gdy utworzysz obiekt Provider przez przekształcenie danych wyjściowych Task, stanie się on niejawną zależność od Provider i będzie tworzony i uruchamiany za każdym razem, gdy wartość obiektu Provider zostanie określona, np. gdy wymagana jest inna wartość Task.Task

Oto przykład zarejestrowania 2 zadań (GitVersionTask i ManifestProducerTask) z odroczeniem tworzenia instancji Task do momentu, gdy będą one faktycznie potrzebne. Wartość wejściowa ManifestProducerTask jest ustawiona na Provider uzyskane z danych wyjściowych GitVersionTask, więc ManifestProducerTask pośrednio zależy od wartości 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 zostaną jawnie żądane. Może się to zdarzyć w ramach wywołania Gradle, na przykład po uruchomieniu ./gradlew debugManifestProducer lub gdy dane wyjściowe funkcji ManifestProducerTask są połączone z innym zadaniem, a 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 bezpośrednio dostępu publicznego do własnych zadań. Są to szczegóły implementacji, które mogą się różnić w zależności od wersji. Zamiast tego AGP udostępnia interfejs API wariantu oraz dostęp do danych wyjściowych zadań lub artefaktów kompilacji, które można odczytywać i przekształcać. Więcej informacji znajdziesz w artykule o interfejsach API wariantów, artefaktach i zadaniach w tym dokumencie.

Etapy kompilacji Gradle

Tworzenie projektu jest z natury skomplikowanym i wymagającym zasobów procesem. Istnieją 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ą być zgodne z rygorystycznymi regułami na każdym etapie kompilacji Gradle: inicjowania, konfiguracji i wykonywania. W tym przewodniku skupimy się na fazach konfiguracji i wykonania. Więcej informacji o wszystkich etapach znajdziesz w przewodniku po cyklu życia kompilacji Gradle.

Faza konfiguracji

Na etapie konfiguracji oceniane są skrypty kompilacji wszystkich projektów wchodzących w skład kompilacji, stosowane są wtyczki i usuwane są zależności kompilacji. Ta faza powinna służyć do konfigurowania kompilacji z użyciem obiektów DSL oraz do leniwego rejestrowania zadań i ich danych wejściowych.

Faza konfiguracji jest zawsze uruchamiana niezależnie od tego, które zadanie jest żądane do uruchomienia, dlatego szczególnie ważne jest, aby było ono przejrzyste i ograniczało obliczenia na podstawie danych wejściowych innych niż same 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 odroczyć do fazy wykonywania jako prawidłowe instancje Task.

Faza wykonania

Na etapie wykonywania wykonywane są żądane zadania i zadania zależne od nich. W szczególności wykonywane są metody klas Task oznaczone symbolem @TaskAction. Podczas wykonywania zadań możesz odczytywać dane wejściowe (np. pliki) i rozwiązywać problemy z leniwym dostawcą, wywołując funkcję Provider<T>.get(). Ten sposób rozwiązania leniwych dostawców uruchamia sekwencję wywołań map() lub flatMap(), które są powiązane z informacjami o zależności zadania zawartymi w dostawcy. Zadania są uruchamiane z opóźnieniem, aby uzyskać wymagane wartości.

Interfejs API wariantów, artefakty i zadania

Interfejs Variant API to mechanizm rozszerzenia we wtyczce Androida do obsługi Gradle, który umożliwia obsługę różnych opcji, które mają wpływ na kompilację Androida, ustawianych zwykle za pomocą DSL w plikach konfiguracji kompilacji. Interfejs Variant API daje też dostęp do pośrednich i końcowych artefaktów tworzonych przez kompilację, np. plików klas, scalonych plików manifestu lub plików APK/AAB.

Proces kompilacji na Androida i punkty rozszerzeń

Podczas interakcji z interfejsem AGP używaj specjalnie przygotowanych punktów rozszerzeń, zamiast rejestrować typowe wywołania zwrotne cyklu życia Gradle (np. afterEvaluate()) lub konfigurować wyraźne zależności Task. Zadania utworzone przez AGP są uznawane za szczegóły implementacji i nie są udostępniane jako publiczny interfejs API. Nie próbuj pobierać instancji obiektów Task, nie odgaduj nazw Task ani nie dodawaj bezpośrednio wywołań zwrotnych lub zależności do tych obiektów Task.

AGP wykonuje poniższe kroki, aby utworzyć i wykonać swoje instancje Task, co z kolei spowoduje utworzenie artefaktów kompilacji. Po głównych instrukcjach tworzenia obiektu Variant następują wywołania zwrotne umożliwiające wprowadzanie zmian w określonych obiektach tworzonych w ramach kompilacji. Pamiętaj, że wszystkie wywołania zwrotne mają miejsce na fazie konfiguracji (opisanej na tej stronie) i muszą działać szybko, co pozwala odłożyć wykonywanie skomplikowanych zadań do wykonania odpowiednich instancji Task na etapie wykonywania.

  1. Analiza składni DSL: na etapie oceniania skryptów kompilacji oraz tworzenia i ustawiania różnych właściwości obiektów DSL Androida z bloku android. Na tym etapie są również rejestrowane wywołania zwrotne interfejsu Variant API opisane w sekcjach poniżej.
  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 wprowadzać zmian.

  4. beforeVariants(): to wywołanie zwrotne może wpływać na tworzenie komponentów i niektóre ich właściwości przez VariantBuilder. Nadal umożliwia wprowadzanie zmian w procesie kompilacji i tworzonych artefaktach.

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

  6. onVariants(): w ramach tego wywołania zwrotnego masz dostęp do utworzonych obiektów Variant i możesz ustawić wartości lub dostawców dla zawartych w nich wartości Property, aby obliczać je z opóźnieniem.

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

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

AGP wprowadza AndroidComponentsExtension, który umożliwia rejestrowanie wywołań zwrotnych dla finalizeDsl(), beforeVariants() i 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 pozostawienie skryptów kompilacji tylko na potrzeby konfiguracji deklaratywnej z wykorzystaniem DSL bloku Androida i przeniesienie dowolnej niestandardowej logiki imperatywnej do buildSrc lub wtyczek zewnętrznych. Możesz też zapoznać się z przykładami (buildSrc) w naszym repozytorium przepisów Gradle na GitHubie, aby dowiedzieć się, jak utworzyć wtyczkę w projekcie. Oto przykład zarejestrowania 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łaniam zwrotnym i typom przypadków użycia obsługiwanych w każdym z nich:

finalizeDsl(callback: (DslExtensionT) -> Unit)

Dzięki temu wywołaniu zwrotnemu masz dostęp do obiektów DSL utworzonych przez analizowanie informacji z bloku android w plikach kompilacji i możesz je modyfikować. Te obiekty DSL zostaną użyte do inicjowania i konfigurowania wariantów na późniejszych etapach kompilacji. Możesz na przykład automatycznie tworzyć nowe konfiguracje lub zastępować właściwości. Pamiętaj jednak, że wszystkie wartości muszą zostać rozpoznane podczas konfiguracji, więc nie mogą one opierać się na żadnych zewnętrznych danych wejściowych. Po zakończeniu tego wywołania zwrotnego obiekty DSL nie są już przydatne i nie można już przechowywać odwołań do nich 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 kompilacji masz dostęp do obiektów VariantBuilder, które określają, które warianty zostaną utworzone i jakie są ich właściwości. Możesz np. automatycznie wyłączać niektóre warianty i ich testy lub zmieniać wartość usługi (np. minSdk) tylko w przypadku wybranego wariantu. Podobnie jak w przypadku finalizeDsl(), wszystkie podane wartości muszą zostać rozpoznane w czasie konfiguracji i nie mogą zależeć od zewnętrznych danych wejściowych. Obiekty VariantBuilder nie mogą być modyfikowane po zakończeniu wywołania zwrotnego beforeVariants().

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

Wywołanie zwrotne beforeVariants() opcjonalnie pobiera metodę VariantSelector, którą możesz uzyskać za pomocą metody selector() w androidComponentsExtension. Możesz go używać do filtrowania komponentów uczestniczących w wywołaniu wywołania zwrotnego na podstawie ich nazwy, typu kompilacji lub rodzaju usługi.

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

onVariants()

Do czasu wywołania onVariants() wszystkie artefakty, które zostaną utworzone przez AGP, są już określone, więc nie można ich wyłączyć. Możesz jednak zmienić niektóre wartości używane przez zadania, ustawiając je dla atrybutów Property w obiektach Variant. Wartości Property będą rozpoznawane tylko po wykonaniu zadań AGP, więc możesz bezpiecznie przekierować je do dostawców z własnych zadań niestandardowych, które będą wykonywać wymagane obliczenia, w tym odczyt z zewnętrznych źródeł, 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() })
}

Dodaj wygenerowane źródła do kompilacji

Wtyczka może uwzględniać kilka rodzajów generowanych źródeł, takich jak:

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

Ten fragment kodu pokazuje, jak za pomocą funkcji addStaticSourceDirectory() dodać do zbioru źródłowego Java niestandardowy folder źródłowy o nazwie ${variant.name}. Łańcuch narzędzi Androida przetworzy 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ć do zbioru źródłowego res katalog z zasobami Androida wygenerowanymi na podstawie zadania niestandardowego. Proces ten jest podobny w przypadku innych typów źródeł.

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 rozszerzenia, który umożliwia odczytywanie lub przekształcanie artefaktów pośrednich i końcowych utworzonych podczas kompilacji. Możesz na przykład przeanalizować ostateczną, scaloną zawartość pliku AndroidManifest.xml w niestandardowym pliku Task lub całkowicie zastąpić ją zawartością pliku manifestu wygenerowanego przez niestandardowe Task.

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

Moc zbioru

Moc zbioru Artifact reprezentuje liczbę instancji FileSystemLocation, czyli liczbę plików lub katalogów typu artefaktu. Aby uzyskać informacje o mocy zbioru artefaktu, sprawdź jego klasę nadrzędną. Artefakty z jedną klasą FileSystemLocation będą podklasą klasy Artifact.Single. Artefakty z wieloma instancjami FileSystemLocation będą podklasą Artifact.Multiple.

FileSystemLocation typ

Aby sprawdzić, czy obiekt Artifact reprezentuje pliki lub katalogi, sprawdź jego z parametrami typ FileSystemLocation. Może to być RegularFile lub Directory.

Obsługiwane operacje

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

  • Transformable: umożliwia używanie elementu Artifact jako danych wejściowych do elementu Task, który wykonuje dowolne przekształcenia i generuje nową wersję Artifact.
  • Appendable: dotyczy tylko artefaktów, które są podklasami Artifact.Multiple. Oznacza to, że można dołączyć do tagu Artifact, co oznacza, że niestandardowy tag 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. Wymienny Artifact można zastąpić całkowicie nową instancją, która jest tworzona jako dane wyjściowe funkcji Task.

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

Różne wtyczki mogą dodawać do potoku dowolną liczbę operacji na artefaktach z wywołania zwrotnego onVariants(), a AGP dba o odpowiednie łączenie łańcuchów, aby wszystkie zadania były uruchamiane we właściwym czasie, a artefakty były prawidłowo tworzone i aktualizowane. Oznacza to, że gdy operacja zmieni dane wyjściowe przez dołączenie, zastąpienie lub przekształcenie, w następnej operacji zobaczysz zaktualizowaną wersję artefaktów jako dane wejściowe itd.

Punktem wejścia do operacji rejestracji jest klasa Artifacts. Poniższy fragment kodu pokazuje, jak uzyskać dostęp do wystąpienia Artifacts z poziomu usługi w obiekcie Variant w wywołaniu zwrotnym onVariants().

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

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

Na koniec przekazujesz typ Artifact do metody reprezentującej wybraną operację na obiekcie *OperationRequest, którą uzyskujesz, 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 to RegularFile. Z tego względu musimy użyć metody wiredWithFiles, która akceptuje 1 odwołanie RegularFileProperty jako dane wejściowe i pojedyncze RegularFileProperty jako dane wyjściowe. W klasie TaskBasedOperation są inne metody wiredWith*, które działają w przypadku innych kombinacji mocy zbioru Artifact i typów FileSystemLocation.

Aby dowiedzieć się więcej o przedłużaniu AGP, zapoznaj się z tymi sekcjami w instrukcji obsługi systemu kompilacji Gradle: