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 un emulatore.
AGP contiene punti di estensione per i plug-in per controllare gli input di compilazione ed estendere la relativa funzionalità tramite nuovi passaggi che possono essere integrati con le attività di compilazione standard. Le versioni precedenti di AGP non avevano API ufficiali chiaramente separate dalle implementazioni interne. A partire dalla versione 7.0, AGP ha 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 designare lo stato delle sue API:
- Interna: non destinata 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 vecchie API e la loro sostituzione con nuove API stabili e un nuovo Domain Specific Language (DSL). Questa evoluzione comprenderà più release AGP e puoi scoprire di più al riguardo nelle tempistiche della migrazione di API AGP/DSL.
Quando le API AGP verranno deprecate, per questa migrazione o per altri scopi, continueranno a essere disponibili nella release principale attuale, ma genereranno avvisi. Le API deprecate verranno rimosse completamente da AGP nella successiva release principale. Ad esempio, se un'API viene ritirata in AGP 7.0, sarà disponibile in quella versione e genererà avvisi. Questa API non sarà più disponibile in AGP 8.0.
Per visualizzare esempi di nuove API utilizzate nelle personalizzazioni comuni delle build, dai un'occhiata alle ricette dei plug-in Android per Gradle. Forniscono esempi di personalizzazioni di compilazione comuni. Puoi anche trovare maggiori 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 di Gradle. Tuttavia, copre l'insieme minimo di concetti necessari per aiutarti a eseguire l'integrazione con le nostre API e rimanda alla documentazione principale di Gradle per ulteriori letture.
Presupponiamo però che tu abbia una conoscenza di base del funzionamento di Gradle, incluso come configurare progetti, modificare file di build, applicare plug-in ed eseguire attività. Per conoscere le nozioni di base di Gradle relative ad AGP, ti consigliamo di consultare Configurare la build. Per informazioni sul framework generale per la personalizzazione dei plug-in Gradle, consulta Sviluppare plug-in Gradle personalizzati.
Glossario dei tipi lazy di Gradle
Gradle offre una serie di tipi che si comportano "pigramente" o aiutano a rimandare i calcoli complessi o la creazione di Task
alle fasi successive della build. Questi tipi sono alla base di molte API Gradle e AGP. Il seguente elenco include i principali tipi di Gradle coinvolti
nell'esecuzione lazy 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 un lambda e produce un elementoProvider
di tipoS
,Provider<S>
. L'argomento lambda amap()
prende il valoreT
e produce il valoreS
. La funzione lambda non viene eseguita immediatamente, ma viene rinviata al momento in cuiget()
viene richiamato nell'elementoProvider<S>
risultante, rendendo l'intera catena lazy.flatMap()
: accetta anche una funzione lambda e produceProvider<S>
, ma la funzione 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 utilizzatomap()
e hai ottenuto un tipo di risultatoProvider<Provider<S>>
, probabilmente dovresti 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 anche impostare 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()
, in modo da assicurarti che le attività vengano create 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 per altre istanzeTask
.
I provider e i relativi metodi di trasformazione sono essenziali per impostare input e output di attività in modo lento, ovvero senza dover creare tutte le attività in anticipo e risolvere i valori.
I provider forniscono anche informazioni sulle dipendenze delle attività. Quando crei un Provider
trasformando un output di Task
, tale Task
diventa una dipendenza implicita di
Provider
e verrà creato ed eseguito ogni volta che il valore dell'elemento Provider
viene
risolto, ad esempio quando un altro Task
lo richiede.
Ecco un esempio di registrazione di due attività, GitVersionTask
e
ManifestProducerTask
, rinviando la creazione delle istanze Task
finché
non 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à verranno eseguite solo se richieste esplicitamente. Questo può accadere nell'ambito di un'invocazione di Gradle, ad esempio se esegui ./gradlew
debugManifestProducer
o se l'output di ./gradlew
debugManifestProducer
è collegato a un'altra attività e il relativo valore diventa obbligatorio.
Sebbene scriverai attività personalizzate che consumano input e/o producono output, AGP non offre accesso pubblico direttamente alle proprie attività. Si tratta di 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 artefatti della build, che puoi leggere e trasformare. Per ulteriori informazioni, consulta la sezione API, artefatti e attività Variant in questo documento.
Fasi di creazione di Gradle
La creazione di un progetto è intrinsecamente un processo complicato e impegnativo in termini di risorse. Esistono varie funzionalità che consentono di evitare la configurazione delle attività, i controlli aggiornati e la funzionalità di memorizzazione nella cache della configurazione che aiutano 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 di Gradle devono rispettare regole rigorose durante ciascuna delle distinte fasi di compilazione 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 della build di Gradle.
Fase di configurazione
Durante la fase di configurazione, vengono valutati gli script di compilazione di tutti i progetti che fanno parte della compilazione, vengono applicati i plug-in e vengono risolte le dipendenze di compilazione. Questa fase deve essere utilizzata per configurare la compilazione utilizzando oggetti DSL e per registrare le attività e i relativi input in modo lazy.
Poiché la fase di configurazione viene eseguita sempre, indipendentemente dall'attività richiesta, è particolarmente importante mantenerla snella e limitare qualsiasi calcolo in modo che non dipenda da input diversi dagli script di compilazione stessi.
In altre parole, non devi eseguire programmi esterni o leggere dalla rete o 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 attività dipendenti. Nello specifico, vengono eseguiti i metodi delle classi Task
contrassegnati con @TaskAction
. Durante l'esecuzione delle attività, puoi leggere dagli input (ad esempio i file) e risolvere i provider lazy chiamando Provider<T>.get()
. Se risolvi i provider lenti in questo modo, viene avviata una sequenza di chiamate map()
o flatMap()
che seguono le informazioni sulle dipendenze delle attività contenute nel provider. Le attività vengono eseguite in modo lazy per materializzare i valori richiesti.
API, artefatti e attività Variant
L'API Variant è un meccanismo di estensione nel plug-in Gradle per Android che consente di manipolare le varie opzioni, normalmente impostate utilizzando il DSL nei file di configurazione della build, che influiscono sulla build di Android. L'API Variant ti consente anche di accedere agli artefatti intermedi e finali creati dalla build, come file di classe, manifest unito o file APK/AAB.
Flusso di compilazione di Android e punti di estensione
Quando interagisci con AGP, utilizza punti di estensione appositamente progettati 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 cercare di ottenere istanze degli oggetti Task
o di indovinare i nomi Task
e di aggiungere direttamente callback o dipendenze 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 compilazione. I passaggi principali per la creazione di oggetti
Variant
sono seguiti da callback che ti consentono di apportare modifiche a determinati
oggetti creati nell'ambito di una compilazione. È importante notare che tutti i callback vengono eseguiti durante la fase di configurazione (descritta in questa pagina) e devono essere eseguiti rapidamente, rimandando invece eventuali operazioni complesse alle istanze Task
appropriate durante la fase di esecuzione.
- Analisi del codice DSL: viene eseguita quando vengono valutati i build script e vengono create e impostate le varie proprietà degli oggetti DSL Android del blocco
android
. Anche i callback dell'API Variant descritti nelle sezioni seguenti vengono registrati durante questa fase. 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 connessione DSL è ora bloccata e non sono più possibili modifiche.
beforeVariants()
: questo callback può influire sui componenti creati e su alcune delle relative proprietà tramiteVariantBuilder
. Consente comunque modifiche al flusso di build e agli artefatti prodotti.Creazione di varianti: l'elenco dei componenti e degli elementi che verranno creati è ora finalizzato e non può essere modificato.
onVariants()
: in questo callback, hai accesso agli oggettiVariant
creati e puoi impostare valori o provider per i valoriProperty
contenuti da calcolare in modo lento.Blocco delle varianti: gli oggetti delle varianti sono ora bloccati e le modifiche non sono più possibili.
Attività create: gli oggetti
Variant
e i relativi valoriProperty
vengono utilizzati per creare le istanzeTask
necessarie per eseguire la compilazione.
AGP introduce un
AndroidComponentsExtension
che 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 conservare gli script di compilazione 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 ai buildSrc
samples nel nostro repository GitHub delle 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 ->
...
}
}
}
Diamo un'occhiata più da vicino ai callback disponibili e al tipo di casi d'uso che il 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 compilazione.
Questi oggetti DSL verranno utilizzati per inizializzare e configurare le varianti nelle fasi successive della build. Ad esempio, puoi creare in modo programmatico nuove configurazioni
o eseguire l'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 di questo callback, gli oggetti DSL non sono più utili e non devi più conservare 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 compilazione, puoi accedere agli oggetti VariantBuilder
, che determinano le varianti che verranno create e le relative proprietà. Ad esempio,
puoi disattivare programmaticamente determinate varianti, i relativi test o modificare il valore di una proprietà (ad esempio minSdk
) solo per una variante scelta. Come per finalizeDsl()
, tutti i valori forniti 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()
richiede facoltativamente un valore VariantSelector
, che puoi
ottenere tramite il metodo selector()
nella androidComponentsExtension
. Puoi utilizzarlo per filtrare i componenti che partecipano alla chiamata del callback in base al loro nome, tipo di build o versione del prodotto.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
Al momento dell'esecuzione della chiamata a onVariants()
, tutti gli elementi che verranno creati da
AGP sono già stati decisi, pertanto 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 Property
verranno risolti solo quando vengono eseguite le attività di AGP, puoi tranquillamente collegarli ai provider dalle tue attività personalizzate che eseguiranno i calcoli richiesti, inclusa 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 tuo plug-in può contribuire con alcuni tipi di origini 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 origini che puoi aggiungere, consulta l'API Sources.
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 al set di origini res
una directory con risorse Android generate da un'attività personalizzata. La procedura è simile per altri tipi di origini.
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 e modificare gli elementi
Oltre a consentirti di modificare proprietà semplici sugli oggetti Variant
, AGP contiene anche un meccanismo di estensione che ti consente di leggere o trasformare gli elementi intermedi e finali prodotti durante la compilazione. Ad esempio, puoi leggere i contenuti finali del file AndroidManifest.xml
unito in un Task
personalizzato per analizzarlo oppure puoi sostituire del tutto 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 FileSystemLocation
oppure il numero di file o directory del tipo di artefatto. Puoi
ottenere informazioni sulla cardinalità di un elemento controllandone la
classe principale: gli elementi con un singolo FileSystemLocation
saranno una sottoclasse di
Artifact.Single
; gli elementi con più istanze di FileSystemLocation
saranno
una sottoclasse di Artifact.Multiple
.
FileSystemLocation
tipo
Per verificare se un Artifact
rappresenta file o directory, osserva il 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 supportate:
Transformable
: consente di utilizzareArtifact
come input per un elementoTask
che esegue trasformazioni arbitrarie e restituisce una nuova versione diArtifact
.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
. UnArtifact
sostituibile può essere sostituito da un'istanza completamente nuova, prodotta come output di unTask
.
Oltre alle tre operazioni di modifica degli elementi, ogni elemento supporta un'operazione get()
(o getAll()
), che restituisce un Provider
con la versione finale dell'elemento (dopo il completamento di tutte le operazioni su di esso).
Più plug-in possono aggiungere un numero qualsiasi di operazioni sugli artefatti alla pipeline
dal callback onVariants()
e AGP garantirà che siano concatenate 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 elementi 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à nell'oggetto Variant
nel callback onVariants()
.
Puoi quindi passare il tuo TaskProvider
personalizzato per ottenere un oggetto
TaskBasedOperation
(1) e utilizzarlo per collegare le relative entrate e uscite 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
e un
RegularFile
. Per questo motivo, è necessario utilizzare il metodo wiredWithFiles
, che
accetta un singolo riferimento a RegularFileProperty
per l'input e un singolo
RegularFileProperty
per l'output. Esistono altri metodi wiredWith*
nella
classe TaskBasedOperation
che funzionano 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:
- 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à