Il plug-in Android per Gradle (AGP) è il sistema di compilazione ufficiale per le applicazioni Android. Include il supporto per la compilazione di molti tipi diversi di origini e il loro collegamento in un'applicazione che puoi eseguire su un dispositivo Android fisico o su un emulatore.
AGP contiene punti di estensione per i plug-in per controllare gli input di build ed estenderne la funzionalità tramite nuovi passaggi che possono essere integrati con le attività di build standard. Le versioni precedenti di AGP non avevano API ufficiali chiaramente separate dalle implementazioni interne. A partire dalla versione 7.0, AGP dispone di un insieme di API ufficiali e stabili su cui puoi fare affidamento.
Ciclo di vita dell'API AGP
AGP segue il ciclo di vita delle funzionalità di Gradle per indicare lo stato delle sue API:
- Interno: non destinato all'uso pubblico
- In incubazione: disponibili per l'uso pubblico, ma non definitivi, il che significa che potrebbero non essere compatibili con le versioni precedenti nella versione finale
- Pubblico: disponibile per l'uso pubblico e stabile
- Ritirata: non più supportata e sostituita da nuove API
Norme sul ritiro
AGP si sta evolvendo con il ritiro delle vecchie API e la loro sostituzione con nuove API stabili e un nuovo linguaggio specifico del dominio (DSL). Questa evoluzione riguarderà più versioni di AGP e puoi scoprire di più nella cronologia della migrazione dell'API/DSL di AGP.
Quando le API AGP vengono ritirate, per questa migrazione o altro, continueranno a essere disponibili nella versione principale corrente, ma genereranno avvisi. Le API deprecate verranno rimosse completamente da AGP nella release principale successiva. Ad esempio, se un'API è deprecata in AGP 7.0, sarà disponibile in quella versione e genererà avvisi. Questa API non sarà più disponibile in AGP 8.0.
Per vedere esempi di nuove API utilizzate nelle personalizzazioni comuni delle build, consulta le ricette del plug-in Android Gradle. Forniscono esempi di personalizzazioni comuni delle build. Puoi anche trovare maggiori dettagli sulle nuove API nella nostra documentazione di riferimento.
Nozioni di base sulla build Gradle
Questa guida non copre l'intero sistema di compilazione Gradle. Tuttavia, copre il set minimo necessario di concetti per aiutarti a eseguire l'integrazione con le nostre API e rimanda alla documentazione principale di Gradle per ulteriori informazioni.
Presupponiamo che tu abbia una conoscenza di base del funzionamento di Gradle, inclusi la configurazione dei progetti, la modifica dei file di build, l'applicazione dei plug-in e l'esecuzione delle attività. Per scoprire le basi di Gradle in relazione ad AGP, ti consigliamo di consultare Configurare la build. Per informazioni sul framework generale per la personalizzazione dei plug-in Gradle, consulta Developing Custom Gradle Plugins.
Glossario dei tipi lazy di Gradle
Gradle offre diversi tipi che si comportano in modo "pigro" o aiutano a posticipare calcoli pesanti o la creazione di Task
alle fasi successive della build. Questi tipi sono alla base di molte
API Gradle e AGP. L'elenco seguente include i principali tipi di Gradle coinvolti
nell'esecuzione differita e i relativi metodi chiave.
Provider<T>
- Fornisce un valore di tipo
T
(dove "T" indica un tipo qualsiasi), che può essere letto durante la fase di esecuzione utilizzandoget()
o trasformato in un nuovoProvider<S>
(dove "S" indica un altro tipo) utilizzando i metodimap()
,flatMap()
ezip()
. Tieni presente cheget()
non deve mai essere chiamato durante la fase di configurazione.map()
: accetta una lambda e produce unProvider
di tipoS
,Provider<S>
. L'argomento lambda dimap()
accetta il valoreT
e produce il valoreS
. La lambda non viene eseguita immediatamente, ma la sua esecuzione viene posticipata al momento in cui viene chiamatoget()
sulProvider<S>
risultante, rendendo pigra l'intera catena.flatMap()
: accetta anche una funzione lambda e produceProvider<S>
, ma la funzione lambda accetta un valoreT
e produceProvider<S>
(anziché produrre il valoreS
direttamente). Utilizza flatMap() quando S non può essere determinato al momento della configurazione e puoi ottenere soloProvider<S>
. In pratica, se hai utilizzatomap()
e hai ottenuto un tipo di risultatoProvider<Provider<S>>
, probabilmente avresti dovuto utilizzareflatMap()
.zip()
: Consente di combinare due istanzeProvider
per produrre un nuovoProvider
, con un valore calcolato utilizzando una funzione che combina i valori delle due istanzeProviders
di input.
Property<T>
- Implementa
Provider<T>
, quindi fornisce anche un valore di tipoT
. A differenza diProvider<T>
, che è di sola lettura, puoi impostare anche un valore perProperty<T>
. Puoi farlo in due modi:- Imposta un valore di tipo
T
direttamente quando è disponibile, senza la necessità di calcoli differiti. - Imposta un altro
Provider<T>
come origine del valore diProperty<T>
. In questo caso, il valoreT
viene materializzato solo quando viene chiamatoProperty.get()
.
- Imposta un valore di tipo
TaskProvider
- Implementa
Provider<Task>
. Per generare unTaskProvider
, utilizzatasks.register()
e nontasks.create()
, per assicurarti che le attività vengano istanziate solo quando sono necessarie. Puoi utilizzareflatMap()
per accedere agli output di unTask
prima che venga creato, il che può essere utile se vuoi utilizzare gli output come input per altre istanze diTask
.Task
I provider e i relativi metodi di trasformazione sono essenziali per configurare gli input e gli output delle attività in modo differito, ovvero senza la necessità di creare tutte le attività in anticipo e risolvere i valori.
I fornitori contengono anche informazioni sulle dipendenze delle attività. Quando crei un Provider
trasformando l'output di un Task
, quest'ultimo diventa una dipendenza implicita del Provider
e verrà creato ed eseguito ogni volta che il valore del Provider
viene risolto, ad esempio quando un altro Task
lo richiede.Task
Ecco un esempio di registrazione di due attività, GitVersionTask
e
ManifestProducerTask
, posticipando la creazione delle istanze Task
fino a quando
non sono effettivamente necessarie. 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à verranno eseguite solo se richieste esplicitamente. Ciò può
accadere nell'ambito di una chiamata Gradle, ad esempio se esegui ./gradlew
debugManifestProducer
o se l'output di ManifestProducerTask
è collegato
a un'altra attività e il suo valore diventa obbligatorio.
Anche se scriverai attività personalizzate che utilizzano input e/o producono output, AGP non offre l'accesso pubblico diretto alle proprie attività. Sono un dettaglio di implementazione soggetto a modifiche da una versione all'altra. AGP offre invece l'API Variant e l'accesso all'output delle sue attività o agli artefatti di build, che puoi leggere e trasformare. Per ulteriori informazioni, consulta la sezione API Variant, artefatti e attività di questo documento.
Fasi di build Gradle
La creazione di un progetto è un processo intrinsecamente complicato e che richiede molte risorse. Esistono varie funzionalità, come l'evitare la configurazione delle attività, i controlli aggiornati e la funzionalità di memorizzazione nella cache della configurazione, che contribuiscono a ridurre al minimo il tempo dedicato a calcoli riproducibili o non necessari.
Per applicare alcune di queste ottimizzazioni, gli script e i plug-in Gradle devono rispettare regole rigorose durante ciascuna delle distinte fasi di build di Gradle: inizializzazione, configurazione ed esecuzione. In questa guida, ci concentreremo sulle fasi di configurazione ed esecuzione. Puoi trovare maggiori informazioni su tutte le fasi nella guida al ciclo di vita della build Gradle.
Fase di configurazione
Durante la fase di configurazione, vengono valutati gli script di build per tutti i progetti che fanno parte della build, vengono applicati i plug-in e vengono risolte le dipendenze della build. Questa fase deve essere utilizzata per configurare la build utilizzando gli oggetti DSL e per registrare le attività e i relativi input in modo differito.
Poiché la fase di configurazione viene sempre eseguita, indipendentemente dall'attività
richiesta, è particolarmente importante mantenerla snella e limitare qualsiasi
calcolo in modo che non dipenda da input diversi dagli script di build stessi.
ovvero non devi eseguire programmi esterni o leggere dalla rete, né
eseguire calcoli lunghi che possono essere rimandati alla fase di esecuzione come istanze
Task
appropriate.
Fase di esecuzione
Nella fase di esecuzione, le attività richieste e le relative attività dipendenti vengono
eseguite. Nello specifico, vengono eseguiti i metodi della classe Task
contrassegnati con @TaskAction
. Durante l'esecuzione dell'attività, puoi leggere gli input (ad esempio i file) e risolvere i provider pigri chiamando Provider<T>.get()
. La risoluzione dei provider
lazy in questo modo avvia una sequenza di chiamate map()
o flatMap()
che seguono
le informazioni sulle dipendenze delle attività contenute nel provider. Le attività vengono eseguite
in modo differito per materializzare i valori richiesti.
API Variant, artefatti e attività
L'API Variant è un meccanismo di estensione nel plug-in Android Gradle che ti consente di manipolare le varie opzioni, normalmente impostate utilizzando il DSL nei file di configurazione della build, che influenzano la build Android. L'API Variant ti consente anche di accedere agli artefatti intermedi e finali creati dalla build, come i file di classe, il manifest unito o i file APK/AAB.
Flusso di build Android e punti di estensione
Quando interagisci con AGP, utilizza punti di estensione appositamente creati anziché registrare i tipici callback del ciclo di vita di Gradle (ad esempio afterEvaluate()
) o configurare dipendenze Task
esplicite. Le attività create da AGP sono considerate
dettagli di implementazione e non sono esposte come API pubblica. Devi evitare
di tentare di ottenere istanze degli oggetti Task
o di indovinare i nomi Task
e
di aggiungere 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 di build. I passaggi principali coinvolti nella
Variant
creazione di oggetti sono seguiti da callback che ti consentono di apportare modifiche a determinati
oggetti creati nell'ambito di una build. È importante notare che tutti i
callback si verificano durante la fase di configurazione
(descritta in questa pagina) e devono essere eseguiti rapidamente, rimandando qualsiasi lavoro complicato
alle istanze Task
appropriate durante la fase di esecuzione.
- Analisi DSL: è il momento in cui vengono valutati gli script di build e vengono creati e impostati
le varie proprietà degli oggetti DSL Android del blocco
android
. Durante questa fase vengono registrati anche i callback dell'API Variant descritti nelle sezioni seguenti. finalizeDsl()
: Callback che consente di modificare gli oggetti DSL prima che vengano bloccati per la creazione di componenti (varianti). Gli oggettiVariantBuilder
vengono creati in base ai dati contenuti negli oggetti DSL.Blocco DSL: la DSL è ora bloccata e non è più possibile apportare modifiche.
beforeVariants()
: questo callback può influenzare i componenti creati e alcune delle loro proprietà tramiteVariantBuilder
. Consente comunque modifiche al flusso di build e agli artefatti prodotti.Creazione di varianti: l'elenco dei componenti e degli artefatti che verranno creati è ora definitivo e non può essere modificato.
onVariants()
: In questo callback, puoi accedere agli oggettiVariant
creati e puoi impostare valori o provider per i valoriProperty
che contengono, in modo che vengano calcolati in modo differito.Blocco delle varianti: gli oggetti variante sono ora bloccati e le modifiche non sono più possibili.
Tasks created (Attività create): gli oggetti
Variant
e i relativi valoriProperty
vengono utilizzati per creare le istanzeTask
necessarie per eseguire la build.
AGP introduce un
AndroidComponentsExtension
che ti consente
di registrare 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, ti consigliamo di utilizzare gli script di build solo per la configurazione dichiarativa utilizzando il DSL del blocco Android e di spostare qualsiasi logica imperativa personalizzata in buildSrc
o in plug-in esterni. Puoi anche dare un'occhiata agli buildSrc
esempi nel nostro repository GitHub di ricette 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 ->
...
}
}
}
Esaminiamo più da vicino i callback disponibili e il tipo di casi d'uso che il tuo plug-in può supportare in ciascuno di essi:
finalizeDsl(callback: (DslExtensionT) -> Unit)
In questo callback, puoi accedere e modificare gli oggetti DSL creati analizzando le informazioni del blocco android
nei file di build.
Questi oggetti DSL verranno utilizzati per inizializzare e configurare le varianti nelle fasi successive della build. Ad esempio, puoi creare programmaticamente nuove configurazioni o sostituire le proprietà, ma tieni presente che tutti i valori devono essere risolti al momento della configurazione, quindi non devono dipendere da input esterni.
Al termine dell'esecuzione di questo callback, gli oggetti DSL non sono più utili e
non devi più conservare i riferimenti o modificare i relativi 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, ottieni l'accesso agli oggetti VariantBuilder
, che
determinano le varianti che verranno create e le relative proprietà. Ad esempio,
puoi disattivare a livello di programmazione determinate varianti, i relativi test o modificare il valore di una proprietà (ad esempio, minSdk
) solo per una variante scelta. Simile a
finalizeDsl()
, tutti i valori che fornisci devono essere risolti al momento della configurazione
e non dipendere da input esterni. Gli oggetti VariantBuilder
non devono essere
modificati al termine dell'esecuzione del callback beforeVariants()
.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
Il callback beforeVariants()
accetta facoltativamente un VariantSelector
, che puoi
ottenere tramite il metodo selector()
su androidComponentsExtension
. Puoi
utilizzarlo per filtrare i componenti che partecipano all'invocazione del callback in base
al nome, al tipo di build o alla variante del prodotto.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
Quando viene chiamato onVariants()
, tutti gli artefatti che verranno creati da
AGP sono già stati decisi, quindi non puoi più disattivarli. Tuttavia, puoi modificare alcuni dei valori utilizzati per le attività impostandoli per gli attributi Property
negli oggetti Variant
. Poiché i valori di Property
verranno risolti solo quando vengono eseguite le attività di AGP, puoi collegarli in modo sicuro ai provider delle tue attività personalizzate che eseguiranno i calcoli necessari, inclusa la lettura da input esterni come file o la 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 alla build
Il plug-in può contribuire con alcuni tipi di fonti generate, ad esempio:
- Codice dell'applicazione nella directory
java
- Risorse Android nella directory
res
- Risorse Java
nella directory
resources
- Asset Android nella
directory
assets
Per l'elenco completo delle fonti che puoi aggiungere, consulta l'API Sources.
Questo snippet di codice mostra come aggiungere una cartella di origine personalizzata chiamata
${variant.name}
al set di origini Java utilizzando la funzione addStaticSourceDirectory()
. La toolchain Android elabora quindi questa cartella.
onVariants { variant ->
variant.sources.java?.let { java ->
java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
}
}
Per ulteriori dettagli, consulta la ricetta addJavaSource.
Questo snippet di codice mostra come aggiungere una directory con risorse Android
generate da un'attività personalizzata al set di origine res
. La procedura è simile per altri
tipi di fonti.
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.
...
}
}
Per ulteriori dettagli, consulta la ricetta addCustomAsset.
Accedere agli artefatti e modificarli
Oltre a consentirti di modificare le proprietà semplici degli oggetti Variant
, AGP
contiene anche un meccanismo di estensione che ti consente di leggere o trasformare
gli artefatti intermedi e finali prodotti durante la build. Ad esempio, puoi
leggere i contenuti del file AndroidManifest.xml
finale unito in un Task
personalizzato per
analizzarli oppure puoi sostituire completamente i suoi contenuti con quelli di un file manifest
generato dal tuo Task
personalizzato.
Puoi trovare l'elenco degli artefatti attualmente supportati nella documentazione di riferimento
per la classe Artifact
. Ogni tipo di artefatto ha determinate proprietà utili da conoscere:
Cardinalità
La cardinalità di un Artifact
rappresenta il numero di istanze di FileSystemLocation
o il numero di file o directory del tipo di artefatto. Puoi
ottenere informazioni sulla cardinalità di un artefatto controllando la relativa classe
principale: gli artefatti con un singolo FileSystemLocation
saranno una sottoclasse di
Artifact.Single
; gli artefatti con più istanze di FileSystemLocation
saranno
una sottoclasse di Artifact.Multiple
.
FileSystemLocation
tipo
Puoi verificare se un Artifact
rappresenta file o directory esaminando il tipo FileSystemLocation
parametrizzato, che può essere un RegularFile
o un Directory
.
Operazioni supportate
Ogni classe Artifact
può implementare una delle seguenti interfacce per indicare
le operazioni che supporta:
Transformable
: consente di utilizzare unArtifact
come input per unTask
che esegue trasformazioni arbitrarie e restituisce una nuova versione delArtifact
.Appendable
: si applica solo agli artefatti che sono sottoclassi diArtifact.Multiple
. Significa che ilArtifact
può essere aggiunto, ovvero unTask
personalizzato può creare nuove istanze di questo tipo diArtifact
che verranno aggiunte all'elenco esistente.Replaceable
: si applica solo agli artefatti che sono sottoclassi diArtifact.Single
. UnArtifact
sostituibile può essere sostituito da un'istanza completamente nuova, prodotta come output di unTask
.
Oltre alle tre operazioni di modifica degli artefatti, ogni artefatto supporta
un'operazione get()
(o getAll()
),
che restituisce un Provider
con la versione finale dell'artefatto
(dopo il completamento di tutte le operazioni).
Più plug-in possono aggiungere un numero qualsiasi di operazioni sugli artefatti nella pipeline
dal callback onVariants()
e AGP si assicurerà che siano concatenati correttamente in modo
che tutte le attività vengano eseguite al momento giusto e che gli artefatti vengano prodotti e
aggiornati correttamente. Ciò significa che quando un'operazione modifica gli output aggiungendoli, sostituendoli o trasformandoli, l'operazione successiva vedrà la versione aggiornata di questi artefatti come input e così via.
Il punto di ingresso per la registrazione delle operazioni è la classe Artifacts
.
Il seguente snippet di codice mostra come accedere a un'istanza di
Artifacts
da una proprietà dell'oggetto Variant
nel callback onVariants()
.
Puoi quindi passare il tuo TaskProvider
personalizzato per ottenere un oggetto TaskBasedOperation
(1) e utilizzarlo per connettere i relativi input e output utilizzando uno dei metodi wiredWith*
(2).
Il metodo esatto da scegliere dipende dalla cardinalità e
dal tipo di FileSystemLocation
implementato dal Artifact
che vuoi trasformare.
Infine, passi il tipo Artifact
a un metodo che rappresenta l'operazione scelta sull'oggetto *OperationRequest
che ricevi 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
ed è un
RegularFile
. Per questo motivo, dobbiamo utilizzare il metodo wiredWithFiles
, che
accetta un singolo riferimento RegularFileProperty
per l'input e un singolo
RegularFileProperty
per l'output. Esistono altri metodi wiredWith*
nella classe TaskBasedOperation
che funzioneranno per altre combinazioni di cardinalità Artifact
e tipi FileSystemLocation
.
Per scoprire di più sull'estensione di AGP, ti consigliamo di leggere le seguenti sezioni del manuale del sistema di compilazione Gradle:
- Sviluppare plug-in Gradle personalizzati
- Implementare i plug-in Gradle
- Sviluppare tipi di attività Gradle personalizzati
- Configurazione differita
- Evitare la configurazione delle attività