Il plug-in Android Gradle (AGP) è il sistema di build 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 un emulatore.
AGP contiene punti di estensione per i plug-in per controllare gli input delle build e per 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 set di API ufficiali e stabili su cui puoi fare affidamento.
Ciclo di vita dell'API AGP
AGP segue il ciclo di vita delle funzionalità Gradle per designare lo stato delle sue API:
- Interno: non destinato all'uso pubblico
- Incubazione: disponibili per uso pubblico ma non definitiva, il che significa che potrebbero non essere compatibili 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 API precedenti e la loro sostituzione con API nuove e stabili e un nuovo Domain Specific Language (DSL). Questa evoluzione abbraccia più release di AGP e puoi scoprire di più in merito nelle tempistiche della migrazione ad API/DSL AGP.
Quando le API AGP verranno deprecate, per questa migrazione o per altri scopi, continueranno a essere disponibili nella release principale corrente, ma genereranno avvisi. Le API deprecate verranno completamente rimosse da AGP nella versione principale successiva. Ad esempio, se un'API è deprecata in AGP 7.0, sarà disponibile in quella versione e genererà avvisi. L'API non sarà più disponibile in AGP 8.0.
Per vedere esempi di nuove API utilizzate nelle personalizzazioni delle build più comuni, dai un'occhiata alle ricette per i plug-in Android per Gradle. Forniscono esempi di personalizzazioni di build comuni. Puoi anche trovare ulteriori dettagli sulle nuove API nella nostra documentazione di riferimento.
Nozioni di base sulla build di Gradle
Questa guida non tratta l'intero sistema di build Gradle. Tuttavia, tratta l'insieme minimo di concetti necessari per aiutarti a eseguire l'integrazione con le nostre API e contiene link alla documentazione principale di Gradle per ulteriori letture.
Assumiamo una conoscenza di base del funzionamento di Gradle, incluse le modalità di configurazione dei progetti, di modifica dei file di build, di applicazione di plug-in ed esecuzione di attività. Per conoscere le nozioni di base di Gradle in merito ad AGP, ti consigliamo di consultare Configura la tua build. Per informazioni sul framework generale per la personalizzazione dei plug-in Gradle, consulta Sviluppo di plug-in Gradle personalizzati.
Glossario dei tipi lenti Gradle
Gradle offre diversi tipi che si comportano "pigramente" o che consentono di rinviare i calcoli complessi o la creazione di Task
a fasi successive della build. Questi tipi sono alla base di molte
API Gradle e AGP. Di seguito sono riportati i tipi principali di Gradle coinvolti
nell'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 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 un lambda e produce unProvider
di tipoS
,Provider<S>
. L'argomento lambda permap()
prende il valoreT
e produce il valoreS
. La funzione lambda non viene eseguita immediatamente, ma viene differita nel momento in cui viene richiamataget()
nell'elementoProvider<S>
risultante, rendendo pigra l'intera catena.flatMap()
: accetta anche un lambda e produceProvider<S>
, ma il valore lambda prende un valoreT
e produceProvider<S>
(anziché produrre direttamente il valoreS
). Usa flatMap() quando non è possibile determinare S al momento della configurazione e puoi ottenere soloProvider<S>
. In pratica, se hai usatomap()
e hai ottenuto un tipo di risultatoProvider<Provider<S>>
, probabilmente avresti dovuto usareflatMap()
.zip()
: consente di combinare due istanzeProvider
per produrre una nuovaProvider
, 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 anche impostare un valore perProperty<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 diProperty<T>
. In questo caso, il valoreT
viene materializzato solo quando viene richiamato il valoreProperty.get()
.
- Imposta un valore di tipo
TaskProvider
- Implementa
Provider<Task>
. Per generare unTaskProvider
, utilizzatasks.register()
e nontasks.create()
, per assicurarti che venga creata un'istanza delle attività in modo lento solo quando sono necessarie. Puoi utilizzareflatMap()
per accedere agli output di unTask
prima della creazione diTask
, il che può essere utile se vuoi utilizzare gli output come input in altre istanzeTask
.
I provider e i relativi metodi di trasformazione sono essenziali per configurare input e output delle attività in modo lento, ovvero senza dover creare preventivamente tutte le attività e risolvere i valori.
I provider includono anche informazioni sulle dipendenze delle attività. Quando crei un Provider
trasformando un output Task
, questo Task
diventa una dipendenza implicita di Provider
e verrà creato ed eseguito ogni volta che il valore di Provider
viene risolto, ad esempio quando un altro Task
lo richiede.
Ecco un esempio di registrazione di due attività, GitVersionTask
e ManifestProducerTask
, posticipando la creazione delle istanze Task
finché non vengono effettivamente richieste. Il valore di input ManifestProducerTask
è impostato su un Provider
ottenuto dall'output di GitVersionTask
, quindi ManifestProducerTask
dipende in modo implicito 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 sono richieste esplicitamente. Ciò può verificarsi come parte di una chiamata a Gradle, ad esempio, se esegui ./gradlew
debugManifestProducer
o se l'output di ManifestProducerTask
è connesso a un'altra attività e il suo valore diventa obbligatorio.
Anche se scriverai attività personalizzate che consumano input e/o producono output, AGP non offre l'accesso pubblico direttamente alle proprie attività. Sono dettagli di implementazione soggetti a modifiche da una versione all'altra. AGP, invece, offre l'API Variant e l'accesso all'output delle sue attività, o creazione di artefatti, che puoi leggere e trasformare. Per ulteriori informazioni, consulta API varianti, Artifacts e Tasks in questo documento.
Fasi di build Gradle
La creazione di un progetto è intrinsecamente un processo complicato e che richiede risorse. Inoltre, esistono varie funzionalità come la possibilità di evitare la configurazione delle attività, i controlli aggiornati e la funzionalità di memorizzazione nella cache della configurazione, che consente di 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 rigide durante ciascuna delle singole fasi di build di Gradle: inizializzazione, configurazione ed esecuzione. In questa guida ci concentreremo sulle fasi di configurazione ed esecuzione. Puoi trovare ulteriori informazioni su tutte le fasi nella guida al ciclo di vita delle build di 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 dovrebbe essere utilizzata per configurare la build utilizzando oggetti DSL e per registrare sistematicamente le attività e i relativi input.
Poiché la fase di configurazione viene sempre eseguita, indipendentemente dall'attività che deve essere eseguita, è particolarmente importante mantenerla snella e limitare i calcoli in base a input diversi dagli script di build stessi.
Ciò significa che non devi eseguire programmi esterni o leggere dalla rete, né eseguire calcoli lunghi che possono essere differiti alla fase di esecuzione come istanze Task
appropriate.
Fase di esecuzione
Nella fase di esecuzione, vengono eseguite le attività richieste e le relative attività dipendenti. Nello specifico, vengono eseguiti i metodi della classe Task
contrassegnati con @TaskAction
. Durante l'esecuzione dell'attività, puoi leggere dagli input (ad esempio i file) e risolvere i provider lenti chiamando Provider<T>.get()
. La risoluzione dei provider lenti in questo modo avvia una sequenza di chiamate map()
o flatMap()
che seguono le informazioni sulla dipendenza delle attività contenute nel provider. Le attività vengono eseguite
in modo lento per materializzare i valori richiesti.
API, artefatti e attività delle varianti
L'API Variant è un meccanismo di estensione nel plug-in Android Gradle che consente di modificare le varie opzioni, normalmente impostate utilizzando la DSL nei file di configurazione della build, che influenzano la build di Android. L'API Variante consente inoltre 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 (come afterEvaluate()
) o configurare dipendenze Task
esplicite. Le attività create da AGP sono considerate
dettagli di implementazione e non sono esposte come API pubblica. Non
cercare di ottenere le istanze degli oggetti Task
o di indovinare i nomi Task
e
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 della build. I passaggi principali della creazione di oggetti Variant
sono seguiti da callback che consentono di apportare modifiche a determinati oggetti creati come parte di una build. È importante notare che tutti i callback avvengono durante la fase di configurazione (descritta in questa pagina) e devono essere eseguiti rapidamente, rimandando invece qualsiasi lavoro complicato alle istanze Task
corrette durante la fase di esecuzione.
- Analisi DSL: è il momento in cui vengono valutati gli script di build e vengono create e impostate 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 dei componenti (variante). Gli oggettiVariantBuilder
vengono creati in base ai dati contenuti negli oggetti DSL.Blocco DSL: la rete 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 di apportare modifiche al flusso di build e agli artefatti prodotti.Creazione variante: l'elenco di componenti e artefatti che verranno creati è ora finalizzato 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, affinché vengano calcolati in modo lento.Blocco delle varianti: ora gli oggetti delle varianti sono bloccati e non è più possibile apportare modifiche.
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 consente di registrare 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, consigliamo di mantenere gli script di build solo per la configurazione dichiarativa utilizzando la DSL del blocco Android e di spostare qualsiasi logica imperativa personalizzata su buildSrc
o su plug-in esterni. Per scoprire come creare un plug-in nel tuo progetto, puoi anche dare un'occhiata agli esempi di buildSrc
disponibili nel nostro repository GitHub di ricette per Gradle. Ecco un esempio di registrazione dei callback dal codice 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 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 nuove configurazioni o sostituire le proprietà in modo programmatico, ma tieni presente che tutti i valori devono essere risolti al momento della 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ù mantenere i riferimenti a questi oggetti 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 loro proprietà. Ad esempio,
puoi disattivare in modo programmatico determinate varianti o i relativi test oppure modificare il valore di
una proprietà (ad esempio minSdk
) solo per una variante scelta. Analogamente a finalizeDsl()
, tutti i valori forniti devono essere risolti al momento della configurazione e non dipendono da input esterni. Gli oggetti VariantBuilder
non devono essere
modificati al termine dell'esecuzione del callback beforeVariants()
.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
Facoltativamente, il callback beforeVariants()
richiede un VariantSelector
, che puoi
ottenere tramite il metodo selector()
nella androidComponentsExtension
. Puoi utilizzarla per filtrare i componenti che partecipano alla chiamata di callback in base al nome, al tipo di build o alla versione del prodotto.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
Al momento della chiamata di onVariants()
, tutti gli artefatti che verranno creati da
AGP sono già stati decisi e non puoi più disabilitarli. Tuttavia, puoi modificare alcuni dei valori utilizzati per le attività impostandoli per gli attributi Property
negli oggetti Variant
. Poiché i valori Property
verranno risolti solo quando verranno eseguite le attività di AGP, puoi collegarli in sicurezza ai provider dalle tue attività personalizzate che eseguiranno tutti i calcoli richiesti, inclusa la lettura da input esterni come file o dalla 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 creazione
Il plug-in può contribuire ad alcuni tipi di origini generate, ad esempio:
- Codice dell'applicazione nella directory
java
- Risorse Android nella directory
res
- Risorse Java nella directory
resources
- Asset per Android nella
directory
assets
Per l'elenco completo delle origini che puoi aggiungere, consulta l'API delle origini.
Questo snippet di codice mostra come aggiungere una cartella di origine personalizzata denominata ${variant.name}
al set di origine Java utilizzando la funzione addStaticSourceDirectory()
. La toolchain di 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 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.
...
}
}
Per ulteriori dettagli, consulta la ricetta per addCustomAsset.
Accedere e modificare gli artefatti
Oltre a modificare le proprietà semplici degli oggetti Variant
, AGP contiene anche un meccanismo di estensione che consente di leggere o trasformare gli artefatti intermedi e finali prodotti durante la build. Ad esempio, puoi leggere i contenuti finali del file AndroidManifest.xml
uniti in una Task
personalizzata per analizzarli oppure puoi sostituirli completamente 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à che è utile conoscere:
Cardinalità
La cardinalità di una Artifact
rappresenta il numero di istanze FileSystemLocation
oppure il numero di file o directory del tipo di artefatto. Puoi ottenere informazioni sulla cardinalità di un artefatto controllando la relativa classe padre. Gli artefatti con un singolo FileSystemLocation
saranno una sottoclasse di Artifact.Single
; gli artefatti con più istanze FileSystemLocation
saranno una sottoclasse di Artifact.Multiple
.
FileSystemLocation
tipo
Puoi verificare se Artifact
rappresenta file o directory esaminando il tipo di FileSystemLocation
parametrizzato, che può essere RegularFile
o Directory
.
Operazioni supportate
Ogni classe Artifact
può implementare una qualsiasi delle seguenti interfacce per indicare quali operazioni supporta:
Transformable
: consente di utilizzareArtifact
come input per unTask
che esegue trasformazioni arbitrarie su di esso e restituisce una nuova versione dell'Artifact
.Appendable
: si applica solo agli artefatti che sono sottoclassi diArtifact.Multiple
. Significa che è possibile aggiungereArtifact
, ovvero unTask
personalizzato può creare nuove istanze di questo tipoArtifact
che verranno aggiunte all'elenco esistente.Replaceable
: si applica solo agli artefatti che sono sottoclassi diArtifact.Single
. Un elementoArtifact
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 elemento 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 li assicurerà che siano concatenati correttamente, in modo che tutte le attività vengano eseguite al momento giusto e 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 delle operazioni di registrazione è la classe Artifacts
.
Il seguente snippet di codice mostra come accedere a un'istanza di
Artifacts
da una proprietà nell'oggetto Variant
nel callback onVariants()
.
Puoi quindi passare il tuo TaskProvider
personalizzato per ottenere un oggetto
TaskBasedOperation
(1) e utilizzarlo per collegare i suoi input e output usando uno dei
metodi wiredWith*
(2).
Il metodo esatto che devi scegliere dipende dalla cardinalità e dal tipo di FileSystemLocation
implementati 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 funzionano con altre combinazioni di
cardinalità Artifact
e tipi FileSystemLocation
.
Per saperne di più sull'estensione di AGP, ti consigliamo di leggere le seguenti sezioni del manuale del sistema di build Gradle:
- Sviluppo di plug-in Gradle personalizzati
- Implementazione dei plug-in Gradle
- Sviluppo di tipi di attività Gradle personalizzati
- Configurazione lazy
- Evitare la configurazione delle attività