[Gradle-8] Guía de desarrollo del complemento Gradle

1. Introducción

El desarrollo del complemento Gradle ocupa una cierta proporción en el conocimiento avanzado de Android, especialmente en el campo de la optimización del rendimiento, que se cubre básicamente y también está estrechamente relacionado con nuestra compilación y empaquetado diarios. Además, muchos requisitos de contratación claramente requieren el complemento Gradle. ins.Experiencia en desarrollo, por lo que aunque la mayoría de las personas no utilicen el desarrollo de complementos en su desarrollo diario, todavía lo anhelan. Este artículo le ofrece el octavo artículo de la serie Gradle : Guía de desarrollo de complementos, y espero que le brinde beneficios.

2. ¿Qué es el complemento Gradle?

El complemento Gradle (complemento) es un mecanismo para ampliar y personalizar la funcionalidad del sistema de compilación Gradle. Gradle es una poderosa herramienta de automatización de compilación para crear y administrar varios tipos de proyectos, desde aplicaciones Java simples hasta proyectos empresariales complejos de múltiples módulos. Los complementos brindan flexibilidad a Gradle, lo que permite a los desarrolladores agregar comportamientos y funcionalidades personalizados según necesidades específicas.
Los complementos de Gradle pueden realizar una variedad de tareas, incluida la compilación de código, la ejecución de pruebas, el empaquetado de archivos, la generación de documentación y más. Los complementos pueden acceder y operar los modelos de compilación de Gradle, como proyectos, tareas, dependencias, etc., para controlar y personalizar el proceso de compilación.
Gradle proporciona un rico ecosistema de complementos y puede utilizar complementos oficiales existentes o complementos de terceros para mejorar el proceso de compilación. Muchos marcos y herramientas populares, como Android, Spring Boot, Kotlin, etc., tienen los complementos de Gradle correspondientes, lo que hace que la integración con estas pilas de tecnología sea más fácil y eficiente.
Por ejemplo, el conocido complemento de Android com.android.application:

plugins {
    
    
    id 'com.android.application'
}

Al escribir su propio complemento de Gradle, puede personalizar y ampliar el sistema de compilación de Gradle para adaptarlo a las necesidades de su proyecto específico. Puede definir tareas personalizadas, configurar extensiones, operar propiedades del proyecto, aplicar otros complementos, etc. en el complemento. Los complementos hacen que el proceso de construcción sea controlable y personalizable, mejorando así la eficiencia del desarrollo.

3. ¿Por qué escribir complementos?

El significado de escribir complementos:

  1. 封装Al extraer la lógica específica, el proyecto solo necesita ejecutar el complemento y no es necesario colocarlo en un determinado archivo build.gradle, lo que reducirá la legibilidad de build.gradle;
  2. 复用, extraiga la lógica común y, cuando la use, solo necesita aplicar el complemento, no necesita copiarlo una y otra vez y también se puede proporcionar a otros proyectos para su uso;
  3. 定制: Si necesita realizar algunas operaciones personalizadas, como instrumentación y Hook, durante la compilación, también debe utilizar el complemento de compilación;

4. ¿Dónde está escrito el complemento?

Presentamos Gradle Task arriba y mencionamos dónde está escrita la tarea, pero ¿dónde está escrito el complemento?
El complemento se puede escribir en 3 lugares:

  1. Al igual que la tarea, está escrita en el archivo build.gradle y abarca el proyecto actual;
  2. Escrito en buildSrc, el alcance son todos los proyectos del proyecto actual;
  3. Escrito en un proyecto separado, se puede proporcionar a todos los proyectos después del lanzamiento;

Según sus propias necesidades, combinado con el alcance del complemento, puede escribirlo en diferentes ubicaciones.

5. Complementos personalizados

Escribir un complemento es bastante simple: solo necesita implementar Pluginla interfaz y el único applymétodo.
Simplemente lo escribimos directamente en build.gradleel archivo:

class YechaoaPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println("这是插件:${this.class.name}")
    }
}

apply plugin: YechaoaPlugin
//apply(plugin: YechaoaPlugin)

En realidad, esta es una clase en línea.

No olvide aplicar dependencias después de escribir. El método de aplicación en la línea 9 es el método de la interfaz PluginAware� llamado apply()y el parámetro es uno map, que se utiliza para asignar el ID del complemento.
salida de sincronización:

> Configure project :app
这是插件:YechaoaPlugin
...

Como se mencionó en la explicación detallada de Tarea en el artículo anterior , Tarea es un método en Proyecto, por lo que necesitamos crear una Tarea a través del Proyecto. En el ejemplo, la clase YechaoaPlugin implementa la interfaz del complemento e implementa el único método de aplicación. El método de aplicación proporciona un objeto de proyecto, por lo que también podemos crear una tarea en el complemento.

class YechaoaPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println("这是插件:${this.class.name}")
        project.task("YechaoaPluginTask") { task ->
            task.doLast {
                println("这是插件:${this.class.name},它创建了一个Task:${task.name}")
            }
        }
    }
}

Como arriba, creamos una tarea en el complemento. En este momento, la sincronización no ejecutará la impresión en la tarea y la tarea debe ejecutarse por separado.
implementar:

./gradlew YechaoaPluginTask

Producción:

> Task :app:YechaoaPluginTask
这是插件:YechaoaPlugin,它创建了一个Task:YechaoaPluginTask

Bien, la escritura de complementos más básica es así de simple.

Combinando los dos resultados anteriores, ya sea simplemente imprimir en el complemento o crear una tarea en el complemento, cuando confiamos en el complemento YechaoaPlugin, es decir, esta aplicación colocará el complemento, similar al apply plugin: YechaoaPluginTaskContainer PluginContainer�, y al mismo tiempo, esto apply El método apply () de la interfaz del complemento también se ejecuta durante la fase de compilación, por lo que habrá una salida después de ejecutar y construir la sincronización, y la tarea ejecutada también está en el gráfico acíclico dirigido.

6. Extensiones de complementos personalizadas

En el segundo capítulo de la serie Gradle , analizamos android{ }cómo los cierres provienen del código fuente. android{ }Los cierres son una configuración muy familiar. A través de DSL, a menudo configuramos compilaSdk�, buildTypes�, etc.
Al personalizar complementos, a menudo existe la necesidad de este tipo de configuración personalizada. A través de estas configuraciones personalizadas, nuestros complementos pueden proporcionar capacidades más ricas. Estas configuraciones se proporcionan a través de complementos de extensión.

6.1 Definir objetos de extensión

interface YechaoaPluginExtension{
    Property<String> getTitle()
}

Puede ser una interfaz o una clase.

6.2. Agregue la extensión al complemento y úsela

class YechaoaPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println("这是插件:${this.class.name}")
        def extension = project.extensions.create("yechaoa", YechaoaPluginExtension)
        project.task("YechaoaPluginTask") { task ->
            task.doLast {
                println("这是插件${this.class.name},它创建了一个Task:${task.name}")
                println(extension.title.get())
            }
        }
    }
}

project.extensions.create()El método recibe dos parámetros:

  1. El primero es el nombre, como yechaoa, android;
  2. El segundo es extender el objeto y luego devolver este objeto de extensión. Puede obtener parámetros de configuración personalizados a través de los métodos de este objeto de extensión;

6.3 Parámetros de configuración

yechaoa.massage = "【Gradle-8】Gradle插件开发指南"

Una configuración se puede omitir directamente o se puede escribir así

yechaoa {
  	massage = "【Gradle-8】Gradle插件开发指南"
}

Si no se establecen parámetros de configuración, Gradle también proporciona configuraciones de valores predeterminados:

extension.title.convention("默认配置title")

Si es un objeto de clase, defina el definidor/obtenedor.

¿Cómo escribir si hay múltiples configuraciones? Simplemente extienda múltiples propiedades de configuración.

6.4 Expansión anidada

Como se muestra a continuación, también existe defaultConfig { } en Android { }

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

    defaultConfig {
        applicationId "com.yechaoa.gradlex"
        ...
    }
}

La expansión anidada es realmente muy simple, como una muñeca matrioska.

Arriba usamos la interfaz para definir los atributos extendidos, cambiemos el método de escritura y usemos el objeto de clase para definirlo.

6.4.1 Extensión de definición

class YechaoaPluginExtension {
    String title
    int chapter
    SubExtension subExtension

    YechaoaPluginExtension(Project project) {
        subExtension = project.extensions.create('sub', SubExtension.class)
    }
}
class SubExtension {
    String author
}

Defina una clase SubExtension adicional y agréguela a YechaoaPluginExtension cuando se cree una instancia ExtensionContainer�.

Si quieres anidar clases, está bien. Tienen que ser clases en línea, de lo contrario el compilador no podrá reconocerlas.

6.4.2 Obtener atributos extendidos

class YechaoaPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println("这是插件:${this.class.name}")
        def extension = project.extensions.create("yechaoa", YechaoaPluginExtension)
        // 设置默认值 可以定义set()方法 然后在这里set
        project.task("YechaoaPluginTask") { task ->
            task.doLast {
                println("这是插件${this.class.name},它创建了一个Task:${task.name}")
                println("title = ${extension.title}")
                println("chapter = ${extension.chapter}")
                println("author = ${extension.subExtension.author}")
            }
        }
    }
}

En comparación con el ejemplo anterior de definición de interfaz, no hay Propertyobjetos .get()y se ha eliminado la configuración de valores predeterminados. Si lo desea, puede setter/gettersimplemente definir el método en el objeto de clase y la otra lógica permanece sin cambios.

6.4.3 Uso

yechaoa {
    title = "【Gradle-8】Gradle插件开发指南"
    chapter = 8
    sub {
        author = "yechaoa"
    }
}

En la configuración de cierre, hay un sub{ }cierre adicional, que se define en nuestra clase YechaoaPluginExtension.

6.4.4 Ejecución

./gradlew YechaoaPluginTask

6.4.5 Salida

> Task :app:YechaoaPluginTask
title = 【Gradle-8】Gradle插件开发指南
chapter = 8
author = yechaoa

6.4.6 Código completo

class YechaoaPluginExtension {
    String title
    int chapter
    SubExtension subExtension

    YechaoaPluginExtension(Project project) {
        subExtension = project.extensions.create('sub', SubExtension.class)
    }
}
class SubExtension {
    String author
}

class YechaoaPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println("这是插件:${this.class.name}")
        def extension = project.extensions.create("yechaoa", YechaoaPluginExtension)
        // 设置默认值 可以定义set()方法 然后在这里set
        project.task("YechaoaPluginTask") { task ->
            task.doLast {
                println("这是插件${this.class.name},它创建了一个Task:${task.name}")
                println("title = ${extension.title}")
                println("chapter = ${extension.chapter}")
                println("author = ${extension.subExtension.author}")
            }
        }
    }
}

apply plugin: YechaoaPlugin

yechaoa {
    title = "【Gradle-8】Gradle插件开发指南"
    chapter = 8
    sub {
        author = "yechaoa"
    }
}

yechaoa{ }¿Esta configuración te resulta familiar ahora ?

yechaoa {
    title = "【Gradle-8】Gradle插件开发指南"
    chapter = 8
    sub {
        author = "yechaoa"
    }
}

¿Es lo android{ }mismo que una moneda de diez centavos?

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

    defaultConfig {
        applicationId "com.yechaoa.gradlex"
        ...
    }
}

7. Escrito en un proyecto separado.

Nuestro complemento anterior está escrito en build.gradleun archivo, pero generalmente en proyectos reales, para una mejor reutilización, generalmente está escrito en buildSrc o en un proyecto separado.
Existen algunas diferencias entre escribir en el archivo build.gradle y escribir en buildSrc o en un proyecto separado. Veamos cómo escribirlo en un proyecto separado (igual a buildSrc).

Para hacerlo simple, simplemente escriba un complemento que imprima todas las dependencias del proyecto.

7.1 Crear nuevo módulo

Cree un nuevo módulo llamado complemento y seleccione el tipo como Libraryo debajo Java or Kotlin Library
Nuevo módulo.png
. Después de crear un nuevo módulo, habrá un directorio de archivos predeterminado y los archivos redundantes se pueden eliminar.
Directorio predeterminado.png
Podemos ver que hay una carpeta java debajo de la carpeta principal. El complemento Gradle se puede escribir en java o se puede aprender usando kotlin o groovy. Puede crear una nueva interfaz de carpeta correspondiente al idioma en el archivo principal, como la carpeta kotlin. .

7.2 Crear un nuevo archivo y agregar dependencias

7.2.1 Crear una nueva clase

Cree una nueva clase DependenciesPlugin�:
imagen.png
pero aún no puede escribir un complemento en este momento, porque su módulo no depende de las API relacionadas con Gradle.

7.2.2 Agregar dependencias

En el futuro , Gradle 6.4no es necesario agregar gradleApi () para configurar las dependencias del complemento, java-gradle-pluginse puede hacer directamente con un complemento, que agregará automáticamente dependencias al proyecto. Y no necesitas configurar el tuyo en Java como antes . Simplemente configúralo con gradlePlugin. Gradle generará automáticamente el archivo de descripción.javagradleApi()
src/main/resources/META-INF/gradle-plugins/xxx.propertiesimplementation-classMETA-INF

Depende del complemento en el archivo complemento>build.gradle:

plugins {
    id 'java-gradle-plugin'
}

La configuración es la siguiente:

gradlePlugin{
    plugins{
        DependenciesPlugin{
            id = 'com.yechaoa.plugin.dependencies'
            implementationClass = 'com.yechaoa.plugin.DependenciesPlugin'
        }
    }
}
  • id�: ID del complemento al que se hace referencia al aplicar;
  • implementaciónClase:Ruta del complemento;

Antes de Gradle 6.4:

implementation-class=com.yechaoa.plugin.DependenciesPlugin

Porque antes estas carpetas y configuraciones eran todas manuales, lo cual era muy engorroso, en comparación ahora es mucho más divertido.

7.3 Escribir complemento

package com.yechaoa.plugin;


import org.gradle.api.Plugin;
import org.gradle.api.Project;

/**
 * GitHub : https://github.com/yechaoa
 * CSDN : http://blog.csdn.net/yechaoa
 * <p>
 * Created by yechao on 2023/8/8.
 * Describe :
 */
class DependenciesPlugin implements Plugin<Project> {
    
    

    @Override
    public void apply(Project project) {
    
    
        System.out.println(">>>>>>>>  " + this.getClass().getName());
    }
}
  1. Cree una nueva clase para implementar la interfaz del complemento;
  2. Implemente su propia lógica en el método de aplicación, como se muestra en el siguiente ejemplo;

En este punto, el prototipo básico del complemento está disponible.

Agregue dependencias usando:

apply plugin: 'com.yechaoa.plugin.dependencies'

Pero ahora no se puede utilizar en proyectos externos. Si hace referencia directamente a esto, el complemento no se encontrará (no se encontrará).

Plugin with id 'com.yechaoa.plugin.dependencies' not found.

Debido a que este complemento está escrito en un proyecto separado, para ser precisos, no tiene nada que ver con otros proyectos, si desea encontrar este complemento, debe publicarlo.

7.4 Lanzamiento local

La publicación local es mucho más sencilla que la publicación remota y, aunque no es difícil, es engorrosa.

7.4.1 Complemento Maven

En primer lugar, el almacén más utilizado es maven. En el archivo complemento> build.gradle, primero confíe en un complemento lanzado por maven.'maven-publish'

plugins {
    
    
    id 'maven-publish'
}

dependencies {
    
    
    implementation 'com.android.tools.build:gradle:7.3.0'
}

7.4.2 Configuración de versión

Agregar configuración de lanzamiento

group 'com.yechaoa.plugin'
version '1.0.0'

publishing {
    
    
    // 配置Plugin GAV
    publications {
    
    
        maven(MavenPublication) {
    
    
            groupId = group
            artifactId = 'dependencies'
            version = version

            from components.java
        }
    }
    // 配置仓库地址
    repositories {
    
    
        maven {
    
    
            url layout.buildDirectory.dir("maven-repo")
        }
    }
}

7.4.3 Realizar operación de publicación

./gradlew publish

O haga clic en Ejecutar en el panel de visualización de Gradle en el lado derecho de Android Studio publish:
publicar.png

7.4.4 Generar productos

no deseado.png
Bien, ahora hay maven-repouna carpeta para la configuración de la versión local en la carpeta de compilación.
Puede confirmar los metadatos y pomarchivos de Maven nuevamente:

<metadata>
  <groupId>com.yechaoa.plugin</groupId>
  <artifactId>dependencies</artifactId>
  <versioning>
    <latest>1.0.0</latest>
    <release>1.0.0</release>
    <versions>
      <version>1.0.0</version>
    </versions>
    <lastUpdated>20230809154815</lastUpdated>
  </versioning>
</metadata>

7.5 Uso

Bien, el lanzamiento local está completo. Si desea utilizar este complemento, el proceso es el mismo que nuestra dependencia normal de los complementos.
Tres pasos:

  1. Configure la dirección del almacén de complementos en el archivo settings.gradle
pluginManagement {
    
    
    repositories {
    
    
        // ...
        maven {
    
    
            url './maven-repo'
        }
    }
}
  1. Agregue dependencias de complementos en el archivo proyecto>build.gradle
buildscript {
    
    
    dependencies {
    
    
        classpath('com.yechaoa.plugin:dependencies:1.0.0')
    }
}
  1. Depende de nuestro complemento en el archivo app:build.gradle
plugins {
    
    
    id 'com.yechaoa.plugin.dependencies'
}

Todas las configuraciones anteriores se agregan en el módulo de la aplicación, que es el módulo que debe usarse.

Compile para ver el efecto:

> Configure project :app
>>>>>>>>  com.yechaoa.plugin.DependenciesPlugin

Bien, está impreso correctamente, lo que indica que nuestro complemento personalizado está listo para uso externo.

Nota: Cuando se utilizan dependencias locales, primero deben publicarse y luego depender del complemento; de lo contrario, habrá una situación en la que no se podrán encontrar las dependencias.

7.6 Implementación de funciones

El ejemplo anterior es solo una impresión, continúe implementando nuestra función e imprima todas las dependencias.

Hay muchas formas de imprimir dependencias, comogradle命令

./gradlew app:dependencies

Entonces, ¿qué pasa si quiero distinguir entre bibliotecas oficiales y bibliotecas de terceros? Esto ya no es satisfactorio.

Modifiquemos el complemento anterior:

class DependenciesPlugin implements Plugin<Project> {
    
    

    private final String TAG = "DependenciesPlugin >>>>> ";

    @Override
    public void apply(Project project) {
    
    
        System.out.println(TAG + this.getClass().getName());

        DependenciesPluginExtension extension = project.getExtensions().create("printDependencies", DependenciesPluginExtension.class);

        project.afterEvaluate(pro -> {
    
    

            /*
             * 扩展的配置要在 project.afterEvaluate 之后获取哦
             * 因为配置阶段完成,才能读取参数
             * 且配置完成,才能拿到所有的依赖
             */

            // 默认开启打印
            extension.getEnable().convention(false);

            if (extension.getEnable().get()) {
    
    
                // debug/release也可以加配置
                System.out.println(TAG + "已开启依赖打印");

                AppExtension androidExtension = project.getExtensions().getByType(AppExtension.class);

                androidExtension.getApplicationVariants().all(applicationVariant -> {
    
    
                    System.out.println(TAG + ">>>>>>>>  applicationVariant.getName() = " + applicationVariant.getName());
                    // 方式一:build.gradle 文件中添加的依赖
                    Configuration configuration = project.getConfigurations().getByName(applicationVariant.getName() + "CompileClasspath");
                    Set<Dependency> allDependencies = configuration.getAllDependencies();
//                for (Dependency dependency : allDependencies) {
    
    
//                    System.out.println(TAG + "dependency === " + dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion());
//                }

                    List<String> androidLibs = new ArrayList<>();
                    List<String> otherLibs = new ArrayList<>();

                    // 方式二:所有的依赖,包括依赖中的依赖
                    configuration.getResolvedConfiguration().getLenientConfiguration().getAllModuleDependencies().forEach(resolvedDependency -> {
    
    
                        ModuleVersionIdentifier identifier = resolvedDependency.getModule().getId();
                        //System.out.println(TAG + "identifier === " + identifier.getGroup() + ":" + identifier.getName() + ":" + identifier.getVersion());
                        if (identifier.getGroup().contains("androidx") || identifier.getGroup().contains("com.google") || identifier.getGroup().contains("org.jetbrains")) {
    
    
                            androidLibs.add(identifier.getGroup() + ":" + identifier.getName() + ":" + identifier.getVersion());
                        } else {
    
    
                            otherLibs.add(identifier.getGroup() + ":" + identifier.getName() + ":" + identifier.getVersion());
                        }
                    });

                    System.out.println("--------------官方库 start--------------");
                    androidLibs.forEach(System.out::println);
                    System.out.println("--------------官方库 end--------------");

                    System.out.println("--------------三方库 start--------------");
                    otherLibs.forEach(System.out::println);
                    System.out.println("--------------三方库 end--------------");
                });
            } else {
    
    
                System.out.println(TAG + "已关闭依赖打印");
            }
        });

    }
}

Extensiones:

interface DependenciesPluginExtension {
    
    
    Property<Boolean> getEnable();
}

usar:

printDependencies {
    
    
    enable = true
}

resumen:

  1. Primero, se agrega una habilitación de configuración para determinar si se necesitan dependencias de impresión;
  2. Una vez completada la evaluación del proyecto (project.afterEvaluate�), obtenga la configuración del proyecto (Configuration)�;
  3. Obtenga todas las dependencias a través de Configuración (getAllModuleDependencies);
  4. Recorrer para obtener GAV y clasificar;
  5. Finalmente imprímalo;

Una cosa a tener en cuenta aquí es que necesitamos obtener la configuración de la extensión en el método project.afterEvaluate, porque el tiempo de ejecución del complemento de aplicación es anterior a la configuración de la extensión; de lo contrario, no se puede obtener el valor de la configuración de la extensión.

Compile y ejecute la salida:

> Configure project :app
>>>>>>>>  com.yechaoa.plugin.DependenciesPlugin
>>>>>>>>  applicationVariant.getName() = debug

--------------官方库 start--------------
com.google.android.material:material:1.8.0
androidx.appcompat:appcompat:1.5.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10

...(省略部分)
    
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1
androidx.arch.core:core-runtime:2.1.0
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1
--------------官方库 end--------------
--------------三方库 start--------------
com.squareup.okhttp3:okhttp:4.10.0
com.squareup.retrofit2:retrofit:2.9.0
com.squareup.okio:okio:3.0.0
com.squareup.okio:okio-jvm:3.0.0
--------------三方库 end--------------

Bien, se logra el efecto de personalizar el complemento en un proyecto independiente, distinguir todas las dependencias e imprimirlas.

8. Resumen

Primero presentamos el complemento Gradle, luego comenzamos con el método de escritura más básico, luego introdujimos la implementación y el uso de la extensión del complemento y finalmente presentamos el proceso de escritura, publicación y uso externo del complemento en un proyecto independiente con un pequeño ejemplo.
En general, la dificultad es media, pero hay algunos pequeños detalles a los que se debe prestar atención, como dominar el ciclo de vida de Gradle, el proceso de uso de complementos, etc.

9. Finalmente

Algunos amigos informaron que, aunque los artículos anteriores estaban bien escritos, eran un poco largos y no fáciles de digerir. .
Sin embargo, Gradle tiene muchas cosas y se simplificará más adelante según corresponda.

Escribir no es fácil, gracias por su apoyo~

10、GitHub

https://github.com/yechaoa/GradleX

11. Documentos relacionados

Supongo que te gusta

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