[Gradle-4] Ciclo de vida Gradle

1. Ciclo de vida Gradle

O ciclo de vida do Gradle também é um conceito muito importante. Depois de entendê-lo, você entenderá muitas coisas e também poderá fazer alguma "tecnologia negra" para processamento face a face em cada estágio do ciclo de vida.

1.1, três estágios

Gradle avalia e executa a compilação em três fases, ou seja Initialization (初始化), Configuration (配置)e Execution (执行), e qualquer tarefa de compilação executará essas três fases.

  • Durante a fase de inicialização, Gradle determina quais projetos estão incluídos na compilação e cria instâncias de projeto para cada projeto. Para decidir quais projetos serão incluídos na compilação, o Gradle primeiro procura settings.gradle para determinar se esta é uma compilação de projeto único ou de vários projetos. Um único projeto é um módulo e um multiprojeto é projeto+aplicativo+módulo(1+n).
  • Durante a fase de Configuração (configuration), o Gradle avalia todos os scripts de construção contidos no projeto de construção, aplica plug-ins, configura a construção usando o DSL e, finalmente, registra as Tarefas, enquanto registra lentamente suas entradas porque elas não são necessariamente executadas.
  • Por fim, na fase Execution (execução), o Gradle executará o conjunto de tarefas necessárias para a compilação.

1.2. A essência do ciclo de vida

A essência do ciclo de vida é combinar as tarefas (Task) em cada etapa, e depois construir o projeto de acordo com nossas intenções.
TaskÉ o núcleo da construção do Gradle. Seu modelo é um grafo acíclico direcionado (DAG). Existem dependências entre as tarefas, ou seja, o grafo de dependência. O Gradle usará o grafo da tarefa para formar uma sequência de tarefas durante o período de construção, que é o conjunto de tarefas.
A origem desta coleção de tarefas é composta pelas três etapas acima. A primeira é a etapa de inicialização. É necessário esclarecer quais projetos estão envolvidos na construção e, em seguida, a etapa de configuração é analisar a configuração de todos os projetos participantes do construção. Isso inclui o registro de tarefas. A configuração do projeto determina a ordem de execução das tarefas. Por exemplo, se houver uma tarefa personalizada em um subprojeto que dependa de uma tarefa integrada para fazer determinadas coisas, então essa tarefa personalizada a tarefa também deve ser adicionada à coleção.Finalmente, é a etapa de execução, que é executada sequencialmente.A Tarefa na coleção é usada para construir o apk.
Portanto, ao contrário, um apk é composto por muitos arquivos, esses arquivos são combinados pela entrada e saída executadas por Tarefas e mescladas, e quais Tarefas executar especificamente, ou seja, que tipo de pacote imprimir, é determinado pelas três vidas ciclo determinado por estágio.
Legenda DAG:
task-day-examples.png

2. Inicialização

Durante a fase de inicialização, Gradle determina quais projetos estão incluídos na compilação e cria instâncias de projeto para cada projeto. Para determinar quais projetos serão incluídos na compilação, Gradle primeiro procura settings.gradledeterminar se esta é uma compilação de projeto único ou uma compilação de vários projetos.

2.1、settings.gradle

2.1.1、Configurações

No artigo anterior, apresentamos que as chamadas de configuração e método em build.gradle são delegadas a Projectobjetos, e as chamadas de configuração e método em settings.gradle do script de construção também são delegadas a Settingsobjetos.
Uma instância de Settings é criada quando o Gradle é criado e o arquivo de configurações é executado com base nela. Há uma correspondência um-para-um entre a instância Settings e o arquivo settings.gradle.

Configurações: declara a configuração necessária para instanciar e configurar uma hierarquia que participa da construção de instâncias do Projeto.

2.1.2. Gerenciamento de projetos

Gradle suporta compilações de projeto único ou multiprojeto:

  • Compilação de projeto único, o arquivo settings.gradle é opcional;
  • Para construção de vários projetos, o arquivo settings.gradle é necessário e deve estar localizado no diretório raiz do projeto;

Arquivos para compilações de vários projetos settings.gradle:

pluginManagement {
    
    
    repositories {
    
    
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
dependencyResolutionManagement {
    
    
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
    
    
        google()
        mavenCentral()
    }
}
rootProject.name = "GradleX"
include ':app'
include ':lib'

Índice:

.
├── app
│   ...
│   └── build.gradle
├── lib
│   ...
│   └── build.gradle
└── settings.gradle

O núcleo é includeque significa adicionar o projeto especificado à construção. Ele pode apontar para o caminho do módulo contido em nosso projeto ou pode apontar para o caminho absoluto do subprojeto no disco rígido. Isso é muito conveniente quando alternar entre projeto aar e código-fonte, e também acelera a compilação.Um dos meios importantes.

2.1.3. Gerenciamento de plug-ins

Além de gerenciar projetos em settings.gradle, outra coisa importante é gerenciar plugins (Plugin), ou seja pluginManagement, .

2.1.3.1, armazém plug-in

pluginManagement {
    
    
    repositories {
    
    
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

Em pluginManagement, repositorieso endereço do depósito de download exigido pelo plug-in é especificado. Se o plug-in personalizado for publicado em um depósito privado, você precisará adicionar o endereço do depósito privado aqui para localizar seu plug-in.

2.1.3.2, substituição de plug-in

A configuração do pluginManagement é PluginManagementSpecanalisada pela classe de interface, que possui 5 métodos:
PluginManagementSpec.png

O método includeBuild precisa estar disponível após a versão 7.0.

Usamos principalmente resolutionStrategy:

@HasInternalProtocol
public interface PluginResolutionStrategy {
    
    

    /**
     * Adds an action that is executed for each plugin that is resolved.
     * The {@link PluginResolveDetails} parameter contains information about
     * the plugin that was requested and allows the rule to modify which plugin
     * will actually be resolved.
     */
    void eachPlugin(Action<? super PluginResolveDetails> rule);

}

PluginResolutionStrategy permite que ele seja modificado antes de PluginRequest, e tem um callback único eachPlugin, o tipo de parâmetro de eachPlugin é PluginResolveDetails.
PluginResolveDetalhes:

  • getRequested: Obtém o plug-in solicitado e retorna o objeto PluginRequest, incluindo id, versão e informações do módulo;
  • useModule: define o módulo do plugin;
  • useVersion: Defina a versão do plugin;
  • getTarget: o plug-in de destino solicitado;

A substituição de plug-in usa principalmente useModuleo método:

pluginManagement {
    
    
    resolutionStrategy {
    
    
        eachPlugin {
    
    
            if (requested.id.id == "org.gradle.sample") {
    
    
                useModule("com.yechaoa.plugin:gradlex:1.0")
            }
        }
    }
}

2.1.3.3, versão plug-in

A versão do plug-in usa principalmente useVersiono método:

pluginManagement {
    
    
    resolutionStrategy {
    
    
        eachPlugin {
    
    
            if (requested.id.id == "com.yechaoa.plugin") {
    
    
                useVersion("2.0")
            }
        }
    }
}

Depois de definir a versão, não há necessidade de especificar a versão novamente se o plug-in for importado por meio de plug-ins { } em todos os scripts de compilação.

2.2, como encontrar settings.gradle

A importância do settings.gradle foi apresentada anteriormente, então como o Gradle encontra o arquivo settings.gradle ao compilar?

  1. Primeiro, o arquivo settings.gradle será encontrado no diretório raiz do projeto, caso não seja encontrado, será construído como um único projeto.
  2. Se for encontrado, verificará novamente a validade da configuração include, se não for legal, continuará sendo construído como um único projeto, se for legal, será construído como um multiprojeto.

3. Configuração

Durante a fase de Configuração (configuration), o Gradle avalia todos os scripts de construção contidos no projeto de construção, aplica plug-ins, configura a construção usando o DSL e, finalmente, registra as Tarefas, enquanto registra lentamente suas entradas porque elas não são necessariamente executadas.

Nota: Não importa qual Task seja solicitada a ser executada, a fase de configuração será executada. Portanto, para manter a construção simples e eficiente, é necessário evitar operações demoradas durante a fase de configuração, semelhante ao método onDraw no android.

Simplificando, a fase de configuração é criar um objeto Project, executar nosso arquivo build.gradle e criar um gráfico de dependência Task correspondente com base no código.

3.1、Projeto

Quando o Gradle é construído, um objeto será criado para cada projeto com base na estrutura do projeto analisada a partir do objeto Settings Project. Há uma relação de um para um entre o objeto Project e o arquivo build.gradle.
Antes de Gradle gerar o gráfico de dependência de Task, o objeto Project faz várias coisas:

  • plug-in de importação
  • propriedades de configuração
  • dependências de compilação

3.1.1. Plug-ins de importação

plugins {
    
    
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

plugins é um método do objeto Project, que é usado para definir os plugins usados ​​pelo módulo atual.

3.1.2, propriedades de configuração

android {
    
    
    namespace 'com.yechaoa.gradlex'
    compileSdk 32

    defaultConfig {
    
    
        applicationId "com.yechaoa.gradlex"
        minSdk 23
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    // ...
}

No artigo anterior , analisamos o código-fonte da configuração do android { }. A configuração do android { } é na verdade a configuração id 'com.android.application' do plug- DSLin. Ou seja, todas as configurações no build.gradle são na verdade a configuração do plug-in -in por DSL. Essas configurações afetarão a execução do plug-in. , o que afeta todo o processo de construção.

3.1.3. Dependências de compilação

dependencies {
    
    

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    
    // ...
}

dependencies { } Além da biblioteca oficial, geralmente adicionamos a biblioteca de terceiros necessária, como okhttp, glide e assim por diante.
As próprias dependências de terceiros do módulo podem adicionar diretamente o endereço de download do warehouse em build.gradle:

repositories {
    
    
    mavenCentral()
    // other url
}

Equivalente a subprojetos { } antes de 7.0, dependencyResolutionManagement>repositories em settings.gradle é equivalente a allprojects { } antes de 7.0.

O artigo anterior [Gradle-2] entende que falta um pouco de dependencyResolutionManagement na configuração do GradlerepositoriesMode , ou seja, a estratégia de resolução de dependências do Gradle para todos os projetos { } e subprojetos { }.

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

RepositoriesMode:

  • PREFER_PROJECT: o valor padrão, usando repositórios { } em build.gradle primeiro, ignorando repositórios { } em settings.gradle;
  • PREFER_SETTINGS: Priorizar repositórios { } em settings.gradle, ignorar repositórios { } em build.gradle;
  • FAIL_ON_PROJECT_REPOS: Isso é incrível, indicando que os repositórios { } declarados em build.gradle causarão erros de compilação;

Se houver apenas o módulo de aplicativo, você pode escrever o endereço do warehouse em dependencyResolutionManagement>repositories. Se houver vários módulos e as dependências forem muito diferentes, é recomendável escrevê-los separadamente. Afinal, encontrar dependências do warehouse é demorado consumindo, embora não seja um ponto problemático para compilação ... (ignorável)

É claro que o objeto Project não faz apenas essas coisas, de um modo geral, mas você também pode ter algumas configurações adicionais, como publicação e assim por diante.

4. Execução

Na fase Execution (execução), o Gradle executará a coleção Task necessária para o build.
Na verdade, esta fase é a verdadeira compilação e empacotamento.Para Android, como nosso comum compileDebugJavaWithJavac, mergeDebugNativeLibs e assim por diante.
imagem.png

5. Ciclo de vida do gancho

O Gradle fornece diversos callbacks em vários estágios do ciclo de vida, o que é muito útil para nós quando fazemos o processamento de aspectos.
imagem.png

5.1、Gancho de inicialização

O Hook na fase de inicialização é o objeto Hook Settings. Quando settings.gradle é avaliado (avaliar), já existe um objeto Settings inicializado.
Podemos gradle.settingsEvaluatedadicionar Hook pelo método, por exemplo em settings.gradle:

println("---Gradle:开始初始化了")
gradle.settingsEvaluated {
    println("---Gradle:settingsEvaluated Settings对象评估完毕")
}

Neste momento, você pode obter o objeto Settings. Apresentamos acima que o objeto Settings é usado principalmente para gerenciar projetos e plug-ins. Neste momento, você pode fazer algumas operações globais, como adicionar um determinado plug-in a todos projetos.
gradle.settingsEvaluated é executado gradle.projectsLoaded:

gradle.projectsLoaded {
    println("---Gradle:projectsLoaded 准备加载Project对象了")
}

Quando projectsLoaded é chamado de volta, o objeto Projeto de cada módulo foi criado de acordo com settings.gradle. Podemos nos referir ao objeto Projeto para definir alguns ganchos:

gradle.allprojects{
    beforeEvaluate {
        println("---Gradle:Projec beforeEvaluate Project开始评估,对象是 = "+project.name)
    }
    afterEvaluate {
        println("---Gradle:Projec afterEvaluate Project评估完毕,对象是 = "+project.name)
    }
}

Mas, neste momento, ele ainda pertence ao estágio de inicialização (inicialização) e ainda não atingiu o estágio de configuração (configuração), portanto, o objeto Projeto contém apenas as informações básicas do projeto e as informações de configuração em build.gradle não podem ser obtidos, portanto, os objetos disponíveis neste estágio são Configurações, os objetos Gradle estão disponíveis em qualquer estágio.

5.2、Gancho de configuração

Após a execução da inicialização, ele entrará na fase de Configuração. Na fase de configuração, você pode obter a configuração de todos os projetos envolvidos na construção. Primeiro, será executado build.gradle sob o projeto raiz e, em seguida, build.gradle sob o projeto do módulo.
Neste momento, você pode conectar o objeto Projeto antes e depois da execução. Por ser um objeto Projeto, ele não pode ser escrito em settings.gradle, mas em build.gradle:

project.beforeEvaluate {
    println("---project:beforeEvaluate Project开始评估,对象是 = " + project.name)
}

project.afterEvaluate {
    println("---project:afterEvaluate Project评估完毕,对象是 = " + project.name)
}

Através do log de execução, verifica-se que o método project.beforeEvaluate não foi executado, pois o hook point passou pelo conteúdo do build.gradle, portanto não surtirá efeito.

project.afterEvaluateA execução do callback significa que a avaliação do objeto Projeto está concluída, e as informações de configuração no objeto Projeto podem ser obtidas neste momento. Como o objeto Projeto acaba de ser configurado neste estágio, muitas tarefas dinâmicas são adicionadas à compilação neste estágio.

Depois que todos os objetos do Projeto forem avaliados, ele chamará de volta gradle.projectsEvaluated:

gradle.projectsEvaluated {
    println("---Gradle:projectsEvaluated 所有Project对象评估完毕")
}

Até agora, todos os objetos de um build foram criados, incluindo o objeto Gradle que controla o geral, o objeto Settings que coordena os módulos participantes e o objeto Project de cada submódulo.

5.3、Gancho de Execução

O Gradle executará a Tarefa na fase Execution (execução), podemos adicionar TaskExecutionListenera execução da Tarefa Hook:

gradle.addBuildListener(new TaskExecutionListener(){

    @Override
    void beforeExecute(Task task) {
        println("---Gradle:Task beforeExecute---")

    }

    @Override
    void afterExecute(Task task, TaskState state) {
        println("---Gradle:Task afterExecute---")
    }
})

Disponível antes de 7.3, foi abandonado depois de 7.3 e causará erros de compilação, porque no caso do cache de configuração, para garantir que a API seja consistente, independentemente de o cache de configuração estar habilitado, ele deve ser eliminado. ..

Gradle sacrificou muito para acelerar a compilação, e o desenvolvimento e a adaptação com certeza serão reclamados novamente...

Tarefa é a menor unidade de construção no Gradle e Ação é a menor unidade de execução.
Pode ser adicionado TaskActionListenerà execução da Ação da Tarefa Gancho:

gradle.addBuildListener(new TaskActionListener(){

    @Override
    void beforeActions(Task task) {
        println("---Gradle:Task beforeActions---")
    }

    @Override
    void afterActions(Task task) {
        println("---Gradle:Task afterActions---")
    }
})

Como TaskExecutionListener, ele também foi eliminado e um erro foi relatado durante a compilação.

@deprecated Este tipo não é suportado quando o cache de configuração está ativado.

5.4, ​​o fim da construção

Quando todas as tarefas forem executadas, significa que a construção acabou e o callback será chamado gradle.buildFinished:

gradle.buildFinished {
    println("---Gradle:buildFinished 构建结束了")
}

Também é abandonado, o motivo é o mesmo acima, mas a compilação não informa um erro...

Além de adicionar pontos de gancho na forma de gradle.xxx, ele também pode ser usado gradle.addListener()e o efeito é o mesmo.

gradle.addListener(new BuildListener() {
    @Override
    void settingsEvaluated(Settings settings) {

    }

    @Override
    void projectsLoaded(Gradle gradle) {

    }

    @Override
    void projectsEvaluated(Gradle gradle) {

    }

    @Override
    void buildFinished(BuildResult result) {

    }
})

5.4.1. Produção geral

Observe a saída geral para experimentar ainda mais o ciclo de vida e o processo de compilação do Gradle.

Executing tasks: [:app:assembleDebug] in project /Users/yechao/AndroidStudioProjects/GradleX

---Gradle:开始初始化了
---Gradle:settingsEvaluated Settings对象评估完毕
---Gradle:projectsLoaded 准备加载Project对象了

> Configure project :
---Gradle:Projec beforeEvaluate Project开始评估,对象是 = GradleX
---Gradle:Projec afterEvaluate Project评估完毕,对象是 = GradleX

> Configure project :app
---Gradle:Projec beforeEvaluate Project开始评估,对象是 = app
---Gradle:Projec afterEvaluate Project评估完毕,对象是 = app
---project:afterEvaluate Project评估完毕,对象是 = app
---Gradle:projectsEvaluated 所有Project对象评估完毕

> Task :app:createDebugVariantModel UP-TO-DATE
> Task :app:preBuild UP-TO-DATE
...
> Task :app:assembleDebug
---Gradle:buildFinished 构建结束了

BUILD SUCCESSFUL in 3s
33 actionable tasks: 12 executed, 21 up-to-date

Build Analyzer results available

5.4.2, como adaptar

O que substituir TaskActionListener e buildFinished após serem descartados? O Gradle fornece Build Serviceuma maneira de substituí-los.

O Build Service pode ser usado para receber eventos durante a execução de tarefas. Para fazer isso, crie e registre um serviço de compilação que implemente OperationCompletionListener. Você pode então começar a receber eventos usando o método BuildEventsListenerRegistry no serviço.

Parece mais complicado...
O que isso quer dizer? O que é um BuildEventsListenerRegistry? Como uso 'os métodos'?
Exemplo:

// build.gradle.kts

abstract class BuildListenerService :
    BuildService<BuildListenerService.Params>,
    org.gradle.tooling.events.OperationCompletionListener {

    interface Params : BuildServiceParameters

    override fun onFinish(event: org.gradle.tooling.events.FinishEvent) {
        println("BuildListenerService got event $event")
    }
}

val buildServiceListener = gradle.sharedServices.registerIfAbsent("buildServiceListener", BuildListenerService::class.java) { }

abstract class Services @Inject constructor(
    val buildEventsListenerRegistry: BuildEventsListenerRegistry
)

val services = objects.newInstance(Services::class)

services.buildEventsListenerRegistry.onTaskCompletion(buildServiceListener)

saída:

> Task :service:vp:assemble UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :service:arc:processResources NO-SOURCE
> Task :service:ar:processResources UP-TO-DATE
> Task :service:ara:processResources UP-TO-DATE
BuildListenerService got event Task :service:vp:assemble UP-TO-DATE
BuildListenerService got event Task :assemble UP-TO-DATE
BuildListenerService got event Task :service:arc:processResources skipped
BuildListenerService got event Task :service:ar:processResources UP-TO-DATE
BuildListenerService got event Task :service:ara:processResources UP-TO-DATE
> Task :service:ti:kaptGenerateStubsKotlin UP-TO-DATE
BuildListenerService got event Task :service:ti:kaptGenerateStubsKotlin UP-TO-DATE
> Task :service:ac:kaptGenerateStubsKotlin UP-TO-DATE
BuildListenerService got event Task :service:ac:kaptGenerateStubsKotlin UP-TO-DATE
> Task :service:ti:kaptKotlin UP-TO-DATE
BuildListenerService got event Task :service:ti:kaptKotlin UP-TO-DATE
> Task :service:ti:compileKotlin NO-SOURCE
BuildListenerService got event Task :service:ti:compileKotlin skipped
> Task :service:ti:compileJava NO-SOURCE
BuildListenerService got event Task :service:ti:compileJava skipped
> Task :service:ti:processResources NO-SOURCE

Há também uma amostra de um amigo estrangeiro: BuildService + projectsEvaluated callback example

TaskExecutionListener > BuildEventsListenerRegistry:

@Incubating
public interface BuildEventsListenerRegistry {
    /**
     * Subscribes the given listener to the finish events for tasks, if not already subscribed. The listener receives a {@link org.gradle.tooling.events.task.TaskFinishEvent} as each task completes.
     *
     * <p>The events are delivered to the listener one at a time, so the implementation does not need to be thread-safe. Also, events are delivered to the listener concurrently with
     * task execution and other work, so event handling does not block task execution. This means that a task finish event is delivered to the listener some time "soon" after the task
     * has completed. The events contain timestamps to allow you collect timing information.
     * </p>
     *
     * <p>The listener is automatically unsubscribed when the build finishes.</p>
     *
     * @param listener The listener to receive events. This must be a {@link org.gradle.api.services.BuildService} instance, see {@link org.gradle.api.services.BuildServiceRegistry}.
     */
    void onTaskCompletion(Provider<? extends OperationCompletionListener> listener);
}

buildFinished > OperationCompletionListener:

public interface OperationCompletionListener {
    /**
     * Called when an operation completes.
     */
    void onFinish(FinishEvent event);
}

6. Finalmente

Este artigo apresenta primeiro os três estágios do ciclo de vida do Gradle e o que esses três estágios fazem. O núcleo são três objetos (Gradle, Settings, Project) e, finalmente, apresenta os pontos Hook correspondentes para esses três estágios. Acredito que você pode ver Depois de terminar este artigo, você entenderá melhor o ciclo de vida e o processo de construção do Gradle. Se for útil, não se esqueça de triplicar ~

7、Github

https://github.com/yechaoa/GradleX

8. Documentos relacionados

Acho que você gosta

Origin blog.csdn.net/yechaoa/article/details/130277978
Recomendado
Clasificación