Scrittura dei plug-in Gradle

Il plug-in Android Gradle (AGP) è il sistema di compilazione ufficiale per Android diverse applicazioni. Supporta la compilazione di molti tipi diversi di fonti e di collegarli tra loro in un'applicazione che puoi eseguire su un Dispositivo Android o un emulatore.

AGP contiene punti di estensione per consentire ai plug-in di controllare gli input della build ed estendere i propri la tua funzionalità attraverso nuovi passaggi che possono essere integrati con attività di machine learning. Le versioni precedenti di AGP non avevano API ufficiali chiaramente separate da implementazioni interne. A partire dalla versione 7.0, AGP ha un insieme API stabili ufficiali su cui puoi fare affidamento attiva.

Ciclo di vita dell'API AGP

AGP segue il ciclo di vita delle funzionalità Gradle per indicare lo stato delle sue API:

  • Interno: non destinato all'uso pubblico
  • Incubazione: disponibile per uso pubblico, ma non definitiva, il che significa che potrebbe non essere compatibile con le versioni precedenti nella versione finale
  • Pubblico: disponibile per uso pubblico e stabile
  • Obsoleta: non più supportata e sostituita con nuove API

Norme sul ritiro

AGP si sta evolvendo con il ritiro delle vecchie API e dei loro la sostituzione con nuove API stabili e un nuovo Domain Specific Language (DSL). Questa evoluzione interesserà diverse release AGP e puoi scoprire di più al riguardo in base alle tempistiche della migrazione di API AGP/DSL.

Quando le API AGP vengono deprecate, per questa migrazione o per altri scopi, continuerà a essere disponibile nella release principale attuale, ma genererà avvisi. Le API deprecate verranno completamente rimosse da AGP nelle . Ad esempio, se un'API è deprecata in AGP 7.0, sarà disponibile in quella versione e generare avvisi. L'API non sarà più disponibile in AGP 8.0.

Per vedere esempi di nuove API utilizzate nelle personalizzazioni comuni delle build, dai un'occhiata nelle ricette del plug-in Android per Gradle. Forniscono esempi di personalizzazioni comuni delle build. Puoi anche trovare altre sulle nuove API nel nostro documentazione di riferimento.

Nozioni di base sulla build di Gradle

Questa guida non tratta l'intero sistema di build di Gradle. Tuttavia, copre l'insieme minimo di concetti necessari per aiutarti all'integrazione con le nostre API e link alla documentazione principale di Gradle per ulteriori letture.

Partiamo dal presupposto che tu abbia le conoscenze di base del funzionamento di Gradle, compresa la configurazione progetti, modificare i file di build, applicare plug-in ed eseguire attività. Per saperne di più di Gradle in relazione ad AGP, ti consigliamo di consultare Configurare . Per conoscere il framework generale per la personalizzazione, Plug-in Gradle, consulta Sviluppo di plug-in Gradle personalizzati.

Glossario dei tipi lenti Gradle

Gradle offre vari tipi di comportamenti "pigra", o contribuiscono a rimandare in modo intensivo di dati Task dalla creazione alle fasi successive della build. Questi tipi sono al centro di molte API Gradle e AGP. Il seguente elenco include i principali tipi di Gradle coinvolti l'esecuzione lazy e i relativi metodi chiave.

Provider<T>
Fornisce un valore di tipo T (dove "T" indica qualsiasi tipo), che può essere letto durante la fase di esecuzione get(): o trasformato in un nuovo Provider<S> (dove "S" indica un altro tipo) utilizzando i metodi map(), flatMap() e zip(). Tieni presente che get() deve non verranno mai chiamati durante la fase di configurazione.
  • map(): accetta un lambda e produce un Provider di tipo S, Provider<S>. L'argomento lambda a map() prende il valore T e produce il valore S. La funzione lambda non viene eseguita immediatamente; la sua esecuzione viene differito al momento in cui get() viene chiamato nell'elemento Provider<S> risultante, rendendo l'intera catena pigra.
  • flatMap(): accetta anche una lambda e produce Provider<S>, ma lambda prende un valore T e produce Provider<S> (anziché produrre il valore il valore S direttamente). Usa flatMap() quando non è possibile determinare S in per la configurazione e potrai ottenere solo Provider<S>. In pratica parlando, se hai usato map() e alla fine hai ottenuto un Provider<Provider<S>> di risultato, probabilmente avresti dovuto usare flatMap().
  • zip(): Consente di combinare due istanze Provider per produrre una nuova Provider, con un valore calcolato utilizzando una funzione che combina i valori dalle due istanze Providers di input.
Property<T>
Implementa Provider<T>, quindi fornisce anche un valore di tipo T. Non mi piace con Provider<T>, che è di sola lettura, puoi anche impostare un valore per il parametro Property<T>. Puoi farlo in due modi:
  • Imposta un valore di tipo T direttamente quando è disponibile, senza bisogno di calcoli differiti.
  • Imposta un altro Provider<T> come origine del valore di Property<T>. Nella In questo caso, il valore T viene materializzato solo quando Property.get() è chiamato.
TaskProvider
Implementa Provider<Task>. Per generare un TaskProvider, usa tasks.register() e non tasks.create(), per garantire che venga creata un'istanza solo per le attività con pigrizia quando serve. Puoi utilizzare flatMap() per accedere agli output di un Task prima della creazione di Task; questo può essere utile se vuoi utilizzare gli output come input per altre istanze Task.

I provider e i relativi metodi di trasformazione sono essenziali per impostare gli input e output di attività in modo lento, ovvero senza la necessità creare preventivamente tutte le attività e risolvere i valori.

I provider includono anche informazioni sulle dipendenze delle attività. Quando crei un Provider di trasformando un output Task, Task diventa una dipendenza implicita Provider e verrà creato ed eseguito ogni volta che il valore dell'attributo Provider sarà venga risolto, ad esempio quando un altro Task lo richiede.

Di seguito è riportato un esempio di registrazione di due attività, GitVersionTask e ManifestProducerTask, rinviando la creazione delle istanze Task fino a sono effettivamente richieste. Il valore di input ManifestProducerTask è impostato su un Provider ottenuto dall'output di GitVersionTask, quindi ManifestProducerTask dipende implicitamente da 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))
    }

Queste due attività vengono eseguite solo se vengono richieste esplicitamente. Questo può avvengono come parte di una chiamata Gradle, ad esempio se esegui ./gradlew debugManifestProducer o se l'output di ManifestProducerTask è connesso a un'altra attività e il suo valore diventa obbligatorio.

Mentre scriverai attività personalizzate che consumano input e/o producono output, AGP non offre l'accesso pubblico direttamente alle proprie attività. Si tratta di un i dettagli dell'implementazione sono soggetti a modifiche da una versione all'altra. Invece, AGP offre l'API Variant e l'accesso all'output delle sue attività, oppure build artefatti, che puoi leggere e trasformare. Consulta API Variant, artefatti e attività in questo per ulteriori informazioni.

Fasi di creazione di Gradle

La realizzazione di un progetto è intrinsecamente un processo complicato e che richiede risorse e ci sono varie funzionalità, come evitare la configurazione delle attività, aggiornare dei controlli e la funzionalità di memorizzazione nella cache della configurazione, che aiuta a ridurre al minimo il tempo su calcoli riproducibili o non necessari.

Per applicare alcune di queste ottimizzazioni, gli script e i plug-in Gradle devono rispetta rigide regole durante ciascuna delle diverse fasi di creazione di Gradle: inizializzazione, configurazione ed esecuzione. In questa guida, ci concentreremo le fasi di configurazione ed esecuzione. Puoi trovare ulteriori informazioni su tutti le fasi nella guida al ciclo di vita dello sviluppo di Gradle.

Fase di configurazione

Durante la fase di configurazione, gli script di build per tutti i progetti che fanno parte della build vengono valutati, i plug-in vengono applicati e le dipendenze risolto. Questa fase deve essere utilizzata per configurare la build utilizzando oggetti DSL e per registrare le attività e i relativi input in modo lento.

Poiché la fase di configurazione viene sempre eseguita, indipendentemente dall'attività richiesta di esecuzione, è particolarmente importante mantenerla snella e limitare i calcoli dipendono da input diversi dagli stessi script di build. Vale a dire che non devi eseguire programmi esterni o leggere dalla rete. eseguire calcoli lunghi che possono essere rinviati alla fase di esecuzione come Task istanza.

Fase di esecuzione

Nella fase di esecuzione, le attività richieste e le attività dipendenti eseguito. Nello specifico, i metodi della classe Task contrassegnati con @TaskAction sono eseguito. Durante l'esecuzione dell'attività, è possibile leggere dagli input (come file) e risolvere i problemi dei provider lazy chiamando Provider<T>.get(). Risoluzione dei problemi di pigrizia In questo modo, viene avviata una sequenza di chiamate map() o flatMap() che seguono le informazioni sulle dipendenze dell'attività contenute nel provider. Le attività vengono eseguite per materializzare i valori richiesti.

API Variant, artefatti e attività

L'API Variant è un meccanismo di estensione nel plug-in Android Gradle che consente Puoi modificare le varie opzioni, solitamente impostate utilizzando la connessione DSL nella build di configurazione di Android, che influenzano la build di Android. Anche l'API Variant consente di accedere agli artefatti intermedi e finali creati build, ad esempio i file dei corsi, i file manifest uniti o i file APK/AAB.

Flusso di build Android e punti di estensione

Quando interagisci con AGP, usa punti di estensione creati appositamente della registrazione dei tipici callback del ciclo di vita di Gradle (ad esempio afterEvaluate()) o configurare dipendenze Task esplicite. Le attività create da AGP sono considerate dettagli dell'implementazione e non sono esposti come API pubblica. Devi evitare di recuperare istanze degli oggetti Task o di indovinare i nomi Task e aggiungendo callback o dipendenze direttamente a questi oggetti Task.

AGP completa i seguenti passaggi per creare ed eseguire le sue istanze Task: che a loro volta producono gli artefatti della build. I passaggi principali Variant la creazione di oggetti è seguita da callback che consentono di apportare modifiche di oggetti creati come parte di una build. È importante notare che tutte le vengono eseguiti durante la fase di configurazione (descritti in questa pagina) e devono essere veloci, rinviando le attività complesse alle istanze Task corrette durante la fase di esecuzione.

  1. Analisi DSL: durante la valutazione degli script di build e quando vengono create varie proprietà degli oggetti Android DSL dal blocco android per iniziare. Anche i callback dell'API Variant descritti nelle sezioni seguenti vengono registrati durante questa fase.
  2. finalizeDsl(): Callback che consente di modificare gli oggetti DSL prima che vengano bloccato per la creazione del componente (variante). VariantBuilder oggetti creati in base ai dati contenuti negli oggetti DSL.

  3. Blocco DSL: ora la rete DSL è bloccata e non è più possibile apportare modifiche.

  4. beforeVariants(): questo callback può influire sui componenti creati, e alcune delle loro proprietà, tramite VariantBuilder. Consente comunque modifiche al flusso di build e agli artefatti che vengono prodotti.

  5. Creazione della variante: l'elenco dei componenti e degli artefatti che verranno creato è ora finalizzato e non può essere modificato.

  6. onVariants(): In questo callback, puoi accedere all'elemento Variant creato e puoi impostare valori o provider per i valori Property che che devono essere calcolati con calma.

  7. Blocco delle varianti: gli oggetti delle varianti sono ora bloccati e le modifiche non sono più. possibile.

  8. Attività create: Variant oggetti e i relativi valori Property vengono utilizzati per e creerai le istanze Task necessarie per eseguire la build.

AGP introduce AndroidComponentsExtension che ti consente registri i callback per finalizeDsl(), beforeVariants() e onVariants(). L'estensione è disponibile negli script di build tramite il blocco androidComponents:

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

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

Tuttavia, il nostro consiglio è di mantenere gli script di build solo per la configurazione usando lo standard DSL del blocco Android sposta qualsiasi logica imperativa personalizzata in buildSrc o plug-in esterni. Puoi anche consultare il buildSrc esempi nel nostro repository GitHub di ricette per Gradle per scoprire come creare un plug-in nel tuo progetto. Ecco un esempio di registrazione dei callback dal codice del plug-in:

abstract class ExamplePlugin: Plugin<Project> {

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

Diamo un'occhiata più da vicino ai callback disponibili e al tipo di casi d'uso supportati dal plug-in in ognuno di essi:

finalizeDsl(callback: (DslExtensionT) -> Unit)

Con questo callback, puoi accedere e modificare gli oggetti DSL che creato analizzando le informazioni del blocco android nei file di build. Questi oggetti DSL saranno utilizzati per inizializzare e configurare le varianti in un secondo momento fasi della build. Ad esempio, puoi creare in modo programmatico nuovi configurazioni o di override delle proprietà, ma tieni presente che tutti i valori devono essere risolti in fase di configurazione, quindi non devono fare affidamento su input esterni. Al termine dell'esecuzione del callback, gli oggetti DSL non sono più utili e non devi più contenere riferimenti a questi elementi o modificarne i valori.

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

In questa fase della build, puoi accedere agli oggetti VariantBuilder, che determinano le varianti che verranno create e le relative proprietà. Ad esempio: puoi disattivare in modo programmatico determinate varianti e i relativi test oppure modificare un (ad es. minSdk) solo per una variante scelta. Simile a finalizeDsl(), tutti i valori forniti devono essere risolti in fase di configurazione molto tempo e non dipendono da input esterni. Gli oggetti VariantBuilder non devono essere modificato al termine dell'esecuzione del callback beforeVariants().

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

Il callback beforeVariants() accetta facoltativamente un valore VariantSelector, che puoi ottenibile tramite il metodo selector() su androidComponentsExtension. Puoi utilizzala per filtrare i componenti che partecipano alla chiamata del callback in base a il nome, il tipo di build o la varietà del prodotto.

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

onVariants()

Quando verrà chiamato onVariants(), tutti gli artefatti che verranno creati Le norme AGP sono già state decise, quindi non puoi più disabilitarle. Tuttavia, puoi modificare alcuni dei valori utilizzati per le attività impostandoli per Attributi Property negli oggetti Variant. Poiché i valori Property risolte solo quando le attività di AGP vengono eseguite, puoi tranquillamente collegarle delle tue attività personalizzate, che eseguiranno tutte le operazioni richieste come la lettura da input esterni come file o rete.

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

Contribuire alle origini generate per la build

Il plug-in può contribuire con alcuni tipi di origini generate, ad esempio:

Per l'elenco completo delle fonti che puoi aggiungere, consulta API Sources.

Questo snippet di codice mostra come aggiungere una cartella di origine personalizzata ${variant.name} al set di origini Java utilizzando addStaticSourceDirectory() personalizzata. La Toolchain di Android elabora quindi questa cartella.

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

Consulta la ricetta addJavaSource per ulteriori informazioni.

Questo snippet di codice mostra come aggiungere una directory con risorse Android generate da un'attività personalizzata nel set di origini res. Il processo è simile per altri tipi di origine.

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

Consulta la ricetta addCustomAsset per ulteriori informazioni.

Accesso e modifica degli artefatti

Oltre a consentirti di modificare semplici proprietà degli oggetti Variant, AGP contiene anche un meccanismo di estensione che consente di leggere o trasformare gli artefatti intermedi e finali prodotti durante la creazione. Ad esempio, puoi leggi i contenuti finali del file AndroidManifest.xml uniti in un file Task personalizzato in analizzarlo o sostituirne del tutto il contenuto con quello di un file manifest generate dalla tua Task personalizzata.

Puoi trovare l'elenco degli artefatti attualmente supportati nel riferimento documentazione per la classe Artifact. Ogni tipo di artefatto ha determinate proprietà che è utile conoscere:

Cardinalità

La cardinalità di un Artifact rappresenta il suo numero di FileSystemLocation o il numero di file o directory del tipo di artefatto. Puoi ottenere informazioni sulla cardinalità di un artefatto controllandone l'elemento padre class: gli artefatti con un solo FileSystemLocation saranno una sottoclasse Artifact.Single gli artefatti con più istanze FileSystemLocation essere una sottoclasse di Artifact.Multiple.

FileSystemLocation tipo

Per verificare se un Artifact rappresenta file o directory, osserva la sua di tipo FileSystemLocation con parametri, che può essere RegularFile o Directory.

Operazioni supportate

Ogni classe Artifact può implementare una qualsiasi delle seguenti interfacce per indicare le operazioni che supporta:

  • Transformable: consente di utilizzare Artifact come input per un Task che trasformazioni arbitrarie e restituisce una nuova versione Artifact.
  • Appendable: si applica solo agli artefatti che sono sottoclassi di Artifact.Multiple. Significa che è possibile aggiungere Artifact, ovvero Task personalizzato può creare nuove istanze di questo tipo Artifact che verranno aggiunte all'elenco esistente.
  • Replaceable: si applica solo agli artefatti che sono sottoclassi di Artifact.Single. Un elemento Artifact sostituibile può essere sostituito da un valore completamente nuovo generato come output di un Task.

Oltre alle tre operazioni di modifica degli artefatti, ogni artefatto supporta Un get() (o getAll()) che restituisce un Provider con la versione finale dell'artefatto (al termine di tutte le operazioni).

Più plug-in possono aggiungere un numero qualsiasi di operazioni sugli artefatti nella pipeline dal callback onVariants() e AGP garantirà che siano concatenati correttamente che tutte le attività vengano eseguite al momento giusto e che gli artefatti vengano prodotti correttamente aggiornato. Ciò significa che quando un'operazione modifica qualsiasi output aggiungendo per sostituirli o trasformarli, la prossima operazione vedrà la versione aggiornata di questi artefatti come input e così via.

Il punto di accesso per la registrazione delle operazioni è la classe Artifacts. Il seguente snippet di codice mostra come ottenere l'accesso a un'istanza di Artifacts da una proprietà sull'oggetto Variant in onVariants() di Google.

Puoi quindi trasmettere il tuo TaskProvider personalizzato per ottenere un TaskBasedOperation (1) e utilizzarlo per collegare i suoi ingressi e le sue uscite utilizzando uno dei wiredWith* metodi (2).

Il metodo esatto da scegliere dipende dalla cardinalità e Tipo FileSystemLocation implementato dal Artifact che vuoi trasformare.

Infine, passi il tipo Artifact a un metodo che rappresenta il un'operazione sull'oggetto *OperationRequest che ottieni in cambio, ad esempio toAppendTo(), toTransform() o 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)
}

In questo esempio, MERGED_MANIFEST è un SingleArtifact e un RegularFile. Per questo motivo dobbiamo usare il metodo wiredWithFiles, che accetta un singolo riferimento RegularFileProperty per l'input e un singolo RegularFileProperty per l'output. Esistono altri metodi wiredWith* su la classe TaskBasedOperation che funzionerà per altre combinazioni di Artifact cardinalità e tipi di FileSystemLocation.

Per saperne di più sull'estensione di AGP, ti consigliamo di leggere le seguenti sezioni dal manuale del sistema di compilazione Gradle: