O Plug-in do Android para Gradle (AGP, na sigla em inglês) é o sistema de build oficial para apps Android. Ele inclui compatibilidade para compilar vários tipos diferentes de fontes e vinculá-las a um aplicativo que pode ser executado em um dispositivo Android físico ou um emulador.
O AGP contém pontos de extensão para plug-ins que controlam as entradas de build e estendem a funcionalidade por meio de novas etapas que podem ser integradas a tarefas de build padrão. As versões anteriores do AGP não tinham APIs oficiais claramente separadas de implementações internas. A partir da versão 7.0, o AGP tem um conjunto de APIs oficiais e estáveis confiáveis.
Ciclo de vida da API AGP
O AGP segue o ciclo de vida do recurso do Gradle (link em inglês) para designar o estado das APIs:
- Interna: não se destina ao uso público
- Incubação: disponível para uso público, mas não o final, o que significa que pode ser incompatível com versões anteriores na versão final.
- Pública: disponível para uso público e estável
- Descontinuada: não é mais compatível e foi substituída por novas APIs
Política de descontinuação
O AGP está evoluindo com a descontinuação de APIs antigas e a substituição delas por APIs novas e estáveis e uma nova Linguagem específica de domínio (DSL, na sigla em inglês). Essa evolução abrangerá várias versões do AGP. Saiba mais sobre isso no cronograma de migração da DSL/API AGP.
Quando as APIs AGP forem descontinuadas, nessa migração ou de outra forma, elas continuarão disponíveis na versão principal atual, mas gerarão avisos. As APIs descontinuadas serão totalmente removidas do AGP na próxima versão principal. Por exemplo, se uma API tiver sido descontinuada no AGP 7.0, ela estará disponível nessa versão e gerará avisos. Essa API não estará mais disponível no AGP 8.0.
Para ver exemplos de novas APIs usadas em personalizações de build comuns, consulte as receitas do Plug-in do Android para Gradle (link em inglês). Elas fornecem exemplos de personalizações de build comuns. Você também encontra mais detalhes sobre as novas APIs na nossa documentação de referência.
Conceitos básicos de build do Gradle
Este guia não abrange todo o sistema de build do Gradle. No entanto, aborda o conjunto mínimo de conceitos necessários para ajudar você a se integrar às nossas APIs e vincula a documentação principal do Gradle para mais leitura.
Presumimos um conhecimento básico sobre como o Gradle funciona, incluindo como configurar projetos, editar arquivos de build, aplicar plug-ins e executar tarefas. Para saber mais sobre os conceitos básicos do Gradle em relação ao AGP, recomendamos que você consulte Configurar seu build. Para saber mais sobre a estrutura geral de personalização de plug-ins do Gradle, consulte Desenvolvimento de plug-ins personalizados do Gradle (link em inglês).
Glossário de tipos lentos do Gradle
O Gradle oferece vários tipos que se comportam "lentamente" ou ajudam a adiar
cálculos complexos ou a criação de
Task
(link em inglês) para fases posteriores do build. Esses tipos são a base de muitas
APIs AGP e Gradle. A lista a seguir inclui os principais tipos do Gradle envolvidos
na execução lenta e os respectivos métodos principais (links em inglês).
Provider<T>
- Fornece um valor do tipo
T
(em que "T" significa qualquer tipo), que pode ser lido durante a fase de execução usandoget()
ou transformado em um novoProvider<S>
(em que "S" significa outro tipo) usando os métodosmap()
,flatMap()
ezip()
. Observe queget()
nunca pode ser chamado durante a fase de configuração.map()
: aceita um lambda e produz umProvider
do tipoS
,Provider<S>
. O argumento lambda paramap()
usa o valorT
e produz o valorS
. O lambda não é executado imediatamente. Em vez disso, a execução é adiada para o momento em que oget()
é chamado noProvider<S>
resultante, tornando toda a cadeia lenta.flatMap()
: também aceita um lambda e produzProvider<S>
, mas o lambda usa um valorT
e produzProvider<S>
, em vez de produzir o valorS
diretamente. Use flatMap() quando S não puder ser determinado no momento da configuração e for possível conseguir apenasProvider<S>
. Na prática, se você usoumap()
e teve um tipo de resultadoProvider<Provider<S>>
, isso provavelmente significa que você usouflatMap()
.zip()
: permite combinar duas instânciasProvider
para produzir um novoProvider
, com um valor calculado usando uma função que combina os valores das duas instâncias deProviders
de entrada.
Property<T>
- Implementa o
Provider<T>
, assim também fornece um valor do tipoT
. Diferentemente doProvider<T>
, que é somente leitura, também é possível definir um valor paraProperty<T>
. Há duas maneiras de fazer isso:- Defina um valor do tipo
T
diretamente quando ele estiver disponível, sem a necessidade de cálculos adiados. - Defina outro
Provider<T>
como origem do valor deProperty<T>
. Nesse caso, o valorT
é materializado apenas quandoProperty.get()
é chamado.
- Defina um valor do tipo
TaskProvider
- Implementa
Provider<Task>
. Para gerar umTaskProvider
, usetasks.register()
, e nãotasks.create()
, para garantir que as tarefas só sejam instanciadas lentamente quando forem necessárias. É possível usarflatMap()
para acessar as saídas de umaTask
antes que aTask
seja criada, o que poderá ser útil se você quiser usar as saídas como entradas para outras instâncias deTask
.
Os provedores e os métodos de transformação deles são essenciais para configurar entradas e saídas de tarefas de maneira lenta, ou seja, sem a necessidade de criar todas as tarefas antecipadamente e resolver os valores.
Os provedores também transportam informações de dependência de tarefas. Quando você cria um Provider
transformando a saída de uma Task
, aquela Task
se torna uma dependência implícita do
Provider
e será criada e executada sempre que o valor do Provider
for
resolvido, como quando for necessário para outra Task
.
Veja um exemplo de registro de duas tarefas, GitVersionTask
e
ManifestProducerTask
, enquanto a criação das instâncias de Task
é adiada até que
elas sejam realmente necessárias. O valor de entrada de ManifestProducerTask
é definido como um
Provider
recebido da saída de GitVersionTask
. Portanto,
ManifestProducerTask
depende implicitamente de 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))
}
Essas duas tarefas só serão executadas se forem explicitamente solicitadas. Isso pode
acontecer como parte de uma invocação do Gradle, por exemplo, se você executar ./gradlew
debugManifestProducer
ou se a saída de ManifestProducerTask
estiver conectada
a alguma outra tarefa e o valor dela se tornar obrigatório.
Embora você grave tarefas personalizadas que consomem entradas e/ou produzem saídas, o AGP não oferece acesso público às próprias tarefas diretamente. Elas são um detalhe de implementação sujeito a mudanças de uma versão para outra. Em vez disso, o AGP oferece a API Variant e o acesso à saída das tarefas, ou artefatos de build, que podem ser lidos e transformados. Consulte API Variant, artefatos e tarefas neste documento para mais informações.
Fases de build do Gradle
Criar um projeto é inerentemente um processo complexo e que exige recursos, e há vários recursos, como prevenção da configuração de tarefas, verificações atualizadas e o recurso de armazenamento em cache da configuração, que ajudam a minimizar o tempo gasto em cálculos reproduzíveis ou desnecessários.
Para aplicar algumas dessas otimizações, os scripts e plug-ins do Gradle precisam obedecer regras rígidas durante cada uma das fases de build do Gradle: inicialização, configuração e execução. Neste guia, falaremos sobre as fases de configuração e execução. Você pode encontrar mais informações sobre todas as fases no guia do ciclo de vida de build do Gradle (link em inglês).
Fase de configuração
Durante a fase de configuração, os scripts de build de todos os projetos que fazem parte do build são avaliados, os plug-ins são aplicados e as dependências de build são resolvidas. Essa fase precisa ser usada para configurar o build usando objetos DSL e para registrar as tarefas e as entradas delas lentamente.
Como a fase de configuração sempre é executada, independentemente de qual tarefa for
solicitada para execução, é importante mantê-la enxuta e evitar que os
cálculos dependam de entradas que não sejam os próprios scripts de build.
Ou seja, não execute programas externos, não leia pela rede nem
execute cálculos longos que possam ser adiados para a fase de execução como instâncias de
Task
adequadas.
Fase de execução
Na fase de execução, as tarefas solicitadas e as tarefas dependentes são
executadas. Especificamente, os métodos de classe Task
marcados com @TaskAction
são
executados. Durante a execução da tarefa, é possível ler as entradas (como
arquivos) e resolver provedores lentos chamando Provider<T>.get()
. Resolver provedores
lentos dessa maneira inicia uma sequência de chamadas map()
ou flatMap()
que seguem
as informações de dependência de tarefa contidas no provedor. As tarefas são executadas
lentamente para materializar os valores necessários.
API Variant, artefatos e tarefas
A API Variant é um mecanismo de extensão no Plug-in do Android para Gradle que permite manipular as várias opções, normalmente definidas usando a DSL em arquivos de configuração do build, que influenciam o build do Android. A API Variant também oferece acesso a artefatos intermediários e finais criados pelo build, como arquivos de classe, o manifesto integrado ou arquivos APK/AAB.
Pontos de extensão e fluxo de build do Android
Ao interagir com o AGP, use pontos de extensão especialmente criados em vez
de registrar callbacks do ciclo de vida comuns do Gradle (como afterEvaluate()
) ou
configurar dependências Task
explícitas. As tarefas criadas pelo AGP são consideradas
detalhes de implementação e não são expostas como uma API pública. Evite
tentar receber instâncias dos objetos Task
ou adivinhar os nomes de Task
e
adicionar diretamente callbacks ou dependências a esses objetos Task
.
O AGP conclui as etapas a seguir para criar e executar as instâncias de Task
,
que, por sua vez, produzem os artefatos de build. As principais etapas envolvidas na
criação do objeto
Variant
são seguidas por callbacks que permitem fazer mudanças em determinados
objetos criados como parte de um build. É importante observar que todos os
callbacks ocorrem durante a fase de configuração
(descrita nesta página) e precisam ser executados rapidamente, adiando qualquer trabalho complicado
para instâncias de Task
adequadas durante a fase de execução.
- Análise DSL: é quando os scripts de build são avaliados e as
várias propriedades dos objetos DSL do Android do bloco
android
são criadas e definidas. Os callbacks da API Variant descritos nas seções a seguir também são registrados durante essa fase. finalizeDsl()
: callback que permite mudar os objetos DSL antes de serem bloqueados para a criação do componente (variante). ObjetosVariantBuilder
são criados com base nos dados contidos nos objetos DSL.Bloqueio de DSL: agora a DSL está bloqueada e as mudanças não são mais possíveis.
beforeVariants()
: esse callback pode influenciar quais componentes são criados e algumas propriedades deles, usandoVariantBuilder
. Ele ainda permite modificações no fluxo de build e nos artefatos produzidos.Criação de variantes: a lista de componentes e artefatos que serão criados agora está finalizada e não pode ser modificada.
onVariants()
: nesse callback, você tem acesso aos objetosVariant
criados e pode definir valores ou provedores para os valoresProperty
que eles contêm, para serem calculados lentamente.Bloqueio de variantes: os objetos de variante agora estão bloqueados, e as mudanças não são mais possíveis.
Tarefas criadas: objetos
Variant
e seus valoresProperty
são usados para criar as instâncias deTask
necessárias para executar o build.
O AGP introduz uma
AndroidComponentsExtension
que permite
registrar callbacks para finalizeDsl()
, beforeVariants()
e onVariants()
.
A extensão está disponível em scripts de build por meio do bloco androidComponents
:
// This is used only for configuring the Android build through DSL.
android { ... }
// The androidComponents block is separate from the DSL.
androidComponents {
finalizeDsl { extension ->
...
}
}
No entanto, recomendamos manter os scripts de build somente para configuração
declarativa usando a DSL do bloco android e
mover qualquer lógica imperativa personalizada para buildSrc
(link em inglês) ou plug-ins externos. Você também pode dar uma olhada nos exemplos de buildSrc
(link em inglês) no nosso repositório GitHub de receitas do Gradle para saber como criar um plug-in no seu projeto. Veja um exemplo de como registrar os callbacks pelo código do plug-in:
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
...
}
}
}
Veja com mais detalhes os callbacks disponíveis e os tipos de casos de uso compatíveis com seu plug-in em cada um deles:
finalizeDsl(callback: (DslExtensionT) -> Unit)
Nesse callback, você pode acessar e modificar os objetos DSL que foram
criados analisando as informações do bloco android
nos arquivos de build.
Esses objetos DSL serão usados para inicializar e configurar variantes em fases posteriores
do build. Por exemplo, é possível criar novas configurações
ou modificar propriedades de maneira programática, mas lembre-se de que todos os valores precisam ser
resolvidos no momento da configuração. Portanto, eles não podem depender de entradas externas.
Após a execução desse callback, os objetos DSL não são mais úteis e
você não precisa mais manter referências a eles ou modificar os valores.
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()
Nesse estágio do build, você tem acesso aos objetos VariantBuilder
, que
determinam as variantes que serão criadas e as propriedades delas. Por exemplo,
é possível desativar de maneira programática determinadas variantes e os testes delas ou mudar o
valor de uma propriedade (por exemplo, minSdk
) somente para uma variante escolhida. De modo semelhante a
finalizeDsl()
, todos os valores fornecidos precisam ser resolvidos no momento da configuração
e não dependem de entradas externas. Os objetos VariantBuilder
não podem ser
modificados depois que a execução do callback beforeVariants()
é concluída.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
O callback beforeVariants()
pode ter VariantSelector
, que você pode
receber por meio do método selector()
na androidComponentsExtension
. É possível
usá-lo para filtrar componentes que participam da invocação do callback com base no
nome, tipo de build ou variação de produto.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
No momento em que onVariants()
é chamado, todos os artefatos que serão criados pelo
AGP já estão decididos, portanto, não é mais possível desativá-los. No entanto, você pode
modificar alguns dos valores usados para as tarefas definindo-os para
atributos Property
nos objetos Variant
. Como os valores Property
só
serão resolvidos quando as tarefas do AGP forem executadas, você poderá conectá-las com segurança a
provedores de tarefas personalizadas que realizarão qualquer cálculo
necessário, incluindo a leitura em entradas externas, como arquivos ou a rede.
// 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() })
}
Fornecer origens geradas para o build
Seu plug-in pode fornecer alguns tipos de origens geradas, como:
- Código do aplicativo no diretório
java
- Recursos do Android no
diretório
res
- Recursos Java (link em inglês)
no diretório
resources
- Ativos do Android no
diretório
assets
Para consultar a lista completa de origens que podem ser adicionadas, acesse a página da API Sources.
Este snippet de código mostra como adicionar ao conjunto de origem Java uma pasta personalizada chamada
${variant.name}
usando a função
addStaticSourceDirectory()
. O conjunto de ferramentas do Android processa essa pasta.
onVariants { variant ->
variant.sources.java?.let { java ->
java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
}
}
Consulte o roteiro addJavaSource (link em inglês) para mais detalhes.
Este snippet de código mostra como adicionar um diretório com recursos do Android
gerados por uma tarefa personalizada para o conjunto de origem res
. O processo é semelhante para outros
tipos de origem.
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.
...
}
}
Consulte o roteiro addCustomAsset (link em inglês) para mais detalhes.
Acessar e modificar artefatos
Além de permitir a modificação de propriedades simples nos objetos Variant
, o AGP
também contém um mecanismo de extensão que permite ler ou transformar
artefatos intermediários e finais produzidos durante o build. Por exemplo, você pode
ler o conteúdo final do arquivo AndroidManifest.xml
mesclado em uma Task
personalizada para
analisá-lo ou substituir todo o conteúdo pelo de um arquivo de manifesto
gerado por sua Task
personalizada.
Veja a lista de artefatos atualmente compatíveis na documentação
de referência da classe Artifact
. Cada tipo de artefato tem determinadas propriedades úteis:
Cardinalidade
A cardinalidade de um Artifact
representa o número de instâncias de FileSystemLocation
ou o número de arquivos ou diretórios do tipo de artefato. Para
ver informações sobre a cardinalidade de um artefato, verifique a classe
pai: artefatos com uma única FileSystemLocation
serão uma subclasse de
Artifact.Single
. Os artefatos com várias instâncias de FileSystemLocation
serão uma subclasse de Artifact.Multiple
.
Tipo de FileSystemLocation
Você pode verificar se um Artifact
representa arquivos ou diretórios analisando o
tipo de FileSystemLocation
parametrizado, que pode ser um RegularFile
ou um
Directory
(links em inglês):
Operações compatíveis
Cada classe Artifact
pode implementar as seguintes interfaces para indicar
quais operações são compatíveis:
Transformable
: permite que umArtifact
seja usado como entrada para umaTask
que executa transformações arbitrárias nele e gera uma nova versão doArtifact
.Appendable
: aplica-se apenas a artefatos que são subclasses deArtifact.Multiple
. Isso significa que oArtifact
pode ser anexado, ou seja, umaTask
personalizada pode criar novas instâncias desse tipo deArtifact
que serão adicionadas à lista existente.Replaceable
: aplica-se apenas a artefatos que são subclasses deArtifact.Single
. UmArtifact
substituível pode ser substituído por uma instância totalmente nova, produzida como saída de umaTask
.
Além das três operações de modificação de artefatos, cada artefato é compatível com uma operação
get()
(ou getAll()
),
que retorna um Provider
com a versão final do artefato,
após a conclusão de todas as operações.
Vários plug-ins podem adicionar qualquer número de operações em artefatos ao pipeline
pelo callback onVariants()
, e o AGP garantirá que eles sejam encadeados corretamente
para que todas as tarefas sejam executadas no momento certo e os artefatos sejam produzidos e
atualizados. Isso significa que, quando uma operação mudar alguma saída, anexando-a,
substituindo-a ou transformando-a, a próxima operação verá a versão atualizada
desses artefatos como entradas e assim por diante.
O ponto de entrada para o registro de operações é a classe Artifacts
.
O snippet de código abaixo mostra como conseguir acesso a uma instância de
Artifacts
em uma propriedade no objeto Variant
no callback
onVariants()
.
Você pode transmitir seu TaskProvider
personalizado para receber um objeto
TaskBasedOperation
(1) e usá-lo para conectar as entradas e saídas usando um dos
métodos wiredWith*
(2).
O método exato que você precisa escolher depende da cardinalidade e
do tipo de FileSystemLocation
implementados pelo Artifact
que você quer transformar.
Por fim, você transmite no tipo de Artifact
para um método que representa a operação
escolhida no objeto *OperationRequest
que você recebe de volta, por exemplo,
toAppendTo()
,
toTransform()
ou 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)
}
Nesse exemplo, MERGED_MANIFEST
é um SingleArtifact
e é um
RegularFile
. Por isso, precisamos usar o método wiredWithFiles
, que
aceita uma única referência de RegularFileProperty
para a entrada e uma única
RegularFileProperty
para a saída. Existem outros métodos wiredWith*
na
classe TaskBasedOperation
que funcionarão para outras combinações de cardinalidade de Artifact
e tipos de FileSystemLocation
.
Para saber mais sobre a extensão do AGP, recomendamos a leitura das seguintes seções do manual do sistema de build do Gradle (links em inglês):
- Como desenvolver plug-ins personalizados do Gradle
- Como implementar plug-ins do Gradle
- Como desenvolver tipos de tarefa personalizados do Gradle
- Configuração lenta
- Prevenção da configuração de tarefas