Android 自定义Gradle插件的完整流程

Posted 好人静

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义Gradle插件的完整流程相关的知识,希望对你有一定的参考价值。

前言

         逐步整理的一系列的总结:

        Android Gradle插件开发初次交手(一)

        Android Gradle的基本概念梳理(二)

        android 自定义Gradle插件的完整流程(三)

        Android 自定义Task添加到任务队列(四)

       Android 自定义Gradle插件的多层属性扩展(五)

      Android Gradle 中的Transform(六)

       Android Gradle之Java字节码(七)

       Android Gradle 中的字节码插桩之ASM(八)

      Android Gradle 中的使用ASMified插件生成.class的技巧(九)

      Android Gradle 中的实例之动态修改AndroidManifest文件(十)


        在Android Gradle插件开发初次交手(一)中主要介绍比较的是怎么去在Android Studio搭建一个自定义插件的工程,仅仅是一个插件工程的创建。而这次主要总结下在一个自定义Gradle工程中的几个关键要素的问题,另外在这过程中应该也解释了一些在Android Gradle的基本概念梳理(二)中遗留的问题。

一 生命周期补充

        在 Android Gradle的基本概念梳理(二)一 Gradle构建项目的三个阶段了解到在Gradle进行项目构建的过程中,会有初始化阶段、配置阶段以及执行阶段,在每个阶段都会有相应的生命周期方法回调。

        在   Android Gradle插件开发初次交手(一)3.添加自定义Gradle的相关代码中提到一个自定义插件入口类就是我们定义的实现了Plugin接口的实现类。在实现类中必须复写里面的apply(project)方法,那么该方法到底是什么时候调用过的呢?然后该方法返回的project到底是整个项目工程中的哪个project对象呢?

        通过自己增加一些关键日志梳理了项目工程的生命周期的一个简单流程:

  • (1)在初始化阶段:主要就是创建项目的层次结构,为settings.gradle中创建出Settings对象,同时为配置的module构建出对应的Project对象;
  • (2)在配置阶段和执行阶段:完成每个module的配置,并且生成各个任务的关系依赖图,然后执行该module的所有Task,依次将所有的module进行“配置-执行”
    • 首先配置默认的buildSrc,然后执行里面的Task
    • 其次是root module,完成root module的配置-执行
    • 再就是app module,在该module中:
      • 首先调用到在plugins或apply的方式加入的插件的apply(project)的方法;
      • 然后依次去配置每个插件的build.gradle文件,完成在app依赖的所有module的配置以及生成关系依赖图
      • 最后就是执行app下所有通过plugins或apply的方式加入的module对应的Task

        其编译的时候输出的日志如下:

/** 配置buildSrc */
> Configure project :buildSrc
/** 执行该buildSrc下所有Task */
> Task :buildSrc:copyTask UP-TO-DATE
............
> Task :buildSrc:build UP-TO-DATE
/** 配置root module */
> Configure project :
<<<<<<<settings.gradle<<<<<<<<<<    beforeProject Project name = AndroidPlugin
/** 配置app module */
> Configure project :app
/** 执行每个插件工程的apply() */
<<<<<<<settings.gradle<<<<<<<<<<    beforeProject Project name = app
%%%%%%%%% FirstPluginProject %%%%%%%%% First Plugin
~~~~~~GlobalGradleProject~~~~~~~~    Global Gradle Project 
/** 配置第一个插件 module,然后完成app这个module下的所有task的关系依赖图 */
> Configure project :firstplugin
<<<<<<<settings.gradle<<<<<<<<<<    beforeProject Project name = firstplugin
<<<<<<<settings.gradle<<<<<<<<<<     projectsEvaluated 已经完成Gradle构建的配置阶段
/** 执行app这个module下所有的Task */
> Task :firstplugin:compileJava NO-SOURCE
............
> Task :firstplugin:testClasses UP-TO-DATE

> Task :app:firstTask
..........
> Task :app:assembleDebug
<<<<<<<settings.gradle<<<<<<<<<<    buildFinished  已经完成Gralde构建的执行阶段

        由此可见:

  • (1) 在自定义插件的入口类的apply(project)是在被引入的module配置build.gradle阶段的plugins或apply该插件的时候调用到apply()方法
  • (2)apply()方法返回的这个project对象就是将该插件放入到module对应的那个project对象。

二 Task

        前面的 Android Gradle的基本概念梳理(二)也介绍了project是有一系列的Task组成的,Task就是执行任务的关键元素,每个Task是有一系列的action组成。在 Android Gradle的基本概念梳理(二)1.Project中的Task中主要介绍了两种比较简单的自定义Task的方式:

  • (1)在build.gradle中通过task taskname的方式
  • (2)在build.gradle中通过task taskname(type: SomeType)的方式
  • (3)在java类中通过继承DefaultTask的方式

        其实Task还有另外两种方式:

  • (1)增量Task
  • (2)定义在class生成Dex之间的Transform的Task

1. 增量Task 

        对于增量Task可分成三部分:input输入、需要完成的action、output输出。

         比如JavaCompileTask。主要就是Target JDK Version和源文件作为input,经过JavaCompileTask这个task执行完毕之后,就会输出class文件。               

        增量Task的本质就是在比较构建任务队列中的Task的input和output来判断Task是否为最新的,如果input和output没有发生变化,则Gradle在构建过程中不需要运行该Task,从而提高构建效率。只有input发生变化的时候,才会重新执行Task。

        对于增量Task必须为该Task指定input输入和output输出。 只有对output输出结果产生变化的才可以作为input输入,并且这个input输入要保证同样的input输入只会产生相同的output。

        作为input输入可以是一个属性、文件或者目录。在Gradle中支持三种类型的input类型:

  • (1)简单的一些实现了Serializable接口的类型,如String、基本数据类型等。例如在android中的compileSdkVersion
  • (2)文件类型像File、FileCollection等,如在Project中通过Project.fileTree()封装的目录中的一系列文件、Project.file()封装的单个文件
  • (3)自定义的类型。该自定义类型中又可以含有inputs和outputs属性

        output输出可以是一个目录或者一个或多个文件

2.Task的input相关的注解

         通常作为input的属性、文件或者目录,通常在Task分为三个过程:

  • (1)在Task中定义为属性
  • (2)在Task中增加getter和setter方法
  • (3)为getter方法中增加相应的注解

        对应上述过程体现到代码如下:

public class HandleTemplateTask extends DefaultTask 
        /**
         * 文件格式
         */
        private String fileFormat;
        /**
         * 文件的路径
         */
        private File fileSourceDir;


        @Input
        public String getFileFormat() 
            return fileFormat;
        

        public void setFileFormat(String fileFormat) 
            this.fileFormat = fileFormat;
        

        @InputDirectory
        //@Optional 可添加表示参数可选
        public File getFileSourceDir() 
            return fileSourceDir;
        

        public void setFileSourceDir(File fileSourceDir) 
            this.fileSourceDir = fileSourceDir;
        

    @TaskAction
    public void run() 
        SystemOutPrint.println(" HandleTemplateTask is running ");
        SystemOutPrint.println(" Set the file format is \\" " + getFileFormat());
        SystemOutPrint.println(" Set the file source dir is \\" " + getFileSourceDir());
    

        先总结几个使用过的一些注解:

  • (1)@Input:对应的是一些基本的数据类型或者是实现了Serializable的类型,在build.gradle中可直接按照对应的类型赋值即可;
  • (2)@InputFile:对应输入的要求是一个文件类型,在build.gradle中可通过file("文件路径")的方式直接赋值,赋值的类型必须保持一致,否则会编译不通过,抛出“ Cannot cast ......”
  • (3)@InputDirectory:对应的要求是一个文件夹类型,在build.gradle中可通过file("文件夹路径")的方式直接赋值,赋值的类型必须保持一致,否则会编译不通过,抛出“ Cannot cast ......”
  • (4)@InputFiles:对应的要求是一个文件列表,包括文件和目录,对应类型为Iterable,在build.gradle中可通过files()的方式直接赋值
  • (5)@OutpuFile:一个输出文件,对应类型为File
  • (6)@OutputDirectory:一个输出文件夹,对应类型为File
  • (7)@OutputFiles:对应输出文件列表,对应类型为Map或Iterable
  • (8)@OutputDirectories:对应输出文件夹列表,对应类型为Map或Iterable
  • (9)@Incremental:通常与@InputFiles和@InputDirectory配合使用,用来跟踪文件的变化
  • (10)@TaskAction:该Task具体执行的任务内容

        相应的将Input换成Output,就是output输出的相关注解。

        上面这些注解,Gradle默认会进行参数校验,要求必须对这些参数进行赋值,否则会抛出下面异常:

A problem was found with the configuration of task ':app:handleTemplateTask' (type 'HandleTemplateTask').
> No value has been specified for property 'fileSourceDir'.

       那如果这些参数是可选的,可添加@Optional表示参数可选,可以不对该值进行赋值。

        其他注解的使用方式随着后面的对项目工程的迭代逐渐去了解。

3.Task中的属性扩展

        刚才在2.Task的input相关的注解的实例中也知道了,不管是input输入还是output输出,都是对该自定义插件属性扩展,是在使用该插件的时候,需要进行配置的属性。那么Gradle也提供了方法来进行属性扩展。

  • (1)定义一个TemplateSettingExtension类,增加需要扩展的属性,同时必须增加getter和setter方法
public class TemplateSettingExtension 
    public static final String TAG = "templateSettingExtension";

    private String compileSdk;
    private File interfaceSourceDir;

    public String getCompileSdk() 
        return compileSdk;
    

    public void setCompileSdk(String compileSdk) 
        this.compileSdk = compileSdk;
    

    public File getInterfaceSourceDir() 
        return interfaceSourceDir;
    

    public void setInterfaceSourceDir(File interfaceSourceDir) 
        this.interfaceSourceDir = interfaceSourceDir;
    

  • (2)将该扩展类添加到主插件类的工程中

        在一 生命周期补充中也知道了apply()方法返回的project就是app这个module的project,所以在project中有一个project.getExtensions()的属性,可用来扩展属性。那么现在只需将该TemplateSettingExtension添加到该属性中即可,代码如下:

class FirstPluginProject implements Plugin<Project> 

    @Override
    void apply(Project project) 
        SystemOutPrint.println("================")
        SystemOutPrint.println("First Plugin")
        SystemOutPrint.println("================")
        /**(1)添加 TemplateSettingExtension*/
        createExtensions(project) 
        //.......
     
   / **
     * 创建Extension
     * @param project
     */
    void createExtensions(Project project) 
        //在这里是无法取到  extension的值,因为此时还没有构建到app中的build.gradle
        project.getExtensions().create(TemplateSettingExtension.TAG, TemplateSettingExtension)
    
  • (3)将该Task添加到被依赖module的任务队列中

        因为在  Android Gradle的基本概念梳理(二)遗留的最大问题就是怎么将Task加入到任务队列中进行执行,通过看Android的gradle-core的源码gradle-core/build-system/gradle-core/build.gradle 中定义的一些Task,如:

task javadocJar(type: Jar, dependsOn:javadoc) 
    classifier  'javadoc'
    from        javadoc.destinationDir


        也是依赖了一个已有的任务,然后就可以在任务队列中看到该Task的执行。然后通过自己的多次尝试发现通过在 project.afterEvaluate 项目构建完成,直接在找到一个已有的任务例如"preBuild"(遗留问题:目前在该插件放到'com.android.application',则在"preBuild"之前添加该Task,如果放入到一个“com.android.library”的module中放到哪个task会更好些呢?),就可以将该任务加入到整个app这个module的任务队列中。

          那么现在FirstPluginProject完整代码如下:


class FirstPluginProject implements Plugin<Project> 

    @Override
    void apply(Project project) 
        /**(1)添加 TemplateSettingExtension*/
        createExtensions(project)
        /**(2)将功能的Task添加到app这个project的任务队列中*/
        addHandleTemplateTask(project)
    
    /**
     * 创建Extension
     * @param project
     */
    void createExtensions(Project project) 
        //在这里是无法取到  extension的值,因为此时还没有构建到app中的build.gradle
        project.getExtensions().create(TemplateSettingExtension.TAG, TemplateSettingExtension)
    
    /**
     * 将HandleTemplateTask加入到任务队列中
     * @param project
     */
    void addHandleTemplateTask(Project project) 
        Task task = project.getTasks().create("handleTemplateTask", HandleTemplateTask)

        //这里是返回的app的这个module,然后在app的project的所有tasks中添加该handleTemplateTask
        project.afterEvaluate 
            project.getTasks().matching 
                //如果将该插件放到'com.android.application',则在"preBuild"之前添加该Task
                it.name.equals("preBuild")
            .each 
                it.dependsOn(task)
                setHandleTemplateTaskInputFromExtension(project, task)

            
        
    
    /**
     * 设置HandleTemplateTask的input
     * @param project
     * @param task
     */
    void setHandleTemplateTaskInputFromExtension(Project project, HandleTemplateTask task) 
   
        //项目配置完成之后,就可以获得设置的Extension中的内容
        TemplateSettingExtension extension = project.getExtensions().findByName(TemplateSettingExtension.TAG)
        task.setFileFormat(".java")
        String path = project.getProjectDir().getAbsolutePath() + "/src/main/java/mvp"
        task.setFileSourceDir(extension.interfaceSourceDir)
    

        另外还要注意这里对于属性扩展的这个功能,一定要在 project.afterEvaluate 项目配置完成之后,在从project.getExtensions().findByName()中获取该Extension,否则将无法获取在app这个module下配置内容。

        按照  Android Gradle插件开发初次交手(一)4.插件发布方式进行发布插件,在app这个module的build.gradle中进行配置扩展属性,代码如下:


//自定义插件中的输入属性的设置
templateSettingExtension 
    compileSdk = "1.0.0"
    interfaceSourceDir = file("src/main/java/mvp")

         使用Android Studio编译整个工程,发现Build窗口输出的内容如下:

.............
> Task :app:handleTemplateTask
%%%%%%%%% FirstPluginProject %%%%%%%%%  HandleTemplateTask is running 
%%%%%%%%% FirstPluginProject %%%%%%%%%  Set the file format is " .java
%%%%%%%%% FirstPluginProject %%%%%%%%%  Set the file source dir is " /Users/j1/Documents/android/code/AndroidPlugin/app/src/main/java/mvp

> Task :app:secondTask UP-TO-DATE
.............

        具体的代码已经上传到至https://github.com/wenjing-bonnie/AndroidPlugin.git的firstplugin目录的相关内容。因为逐渐在这基础上进行迭代,可回退到Gradle_3.0该tag下可以查看相关内容。

        具有Transform的Task的另外单独总结。

三 总结

        距离上一篇   Android Gradle的基本概念梳理(二)有点时间,这段时间也最难熬,因为怎么去尝试把Task加入到任务队列中怎么都不成功,网上好多例子都是直接放到通过定义一个task就能执行该Task,我却怎么尝试都不成功(我觉得别人之所以能够成功,应该是用gradle命令是可以的,而我一直依赖的是Android Studio的这个编译环境);还有@Input的属性没有赋值,一直报“No value has been specified for property  ' ' ”编译不通过,找不到解决方案;还有定义成@InputFile @InputDirectory等类型,在build.gradle一直匹配不到合适的类型,等等。但是这些问题都被我这几天给解决了,并且我觉得对于Gradle的理解应该最难的阶段过去了,后面理解学习起来应该会容易好多。

  • 1.自定义插件的入口类的apply()方法会在被依赖的插件通过apply或者plugins将该插件添加到build.gradle的时候执行;
  • 2.apply(project)返回的project是被依赖的module对应的project,所以在定义插件中对该project进行添加属性扩展或者添加task,都是对被依赖的module对应的project的修改;
  • 3.增量Task就是通过比较在构建任务的input和output是不是最新的,来决定是不是将该Task加入到构建任务队列中;
  • 4.如果对Task设置了input属性,Gradle会对其进行参数校验。必须对其赋值,并且类型要保持一致;如果该属性是可选的,可通过@Optional进行注解;
  • 5.可通过project.getExtensions()为自定义插件添加属性扩展,但是该类必须添加setter和getter方法
  • 6.必须将自定义的task通过dependsOn的方式加入到任务队列中,否则该task中被@TaskAction标记的方法不会被执行

        对于具有Transform功能的Task后面再去研究,加油!!

         这段时间狂输出一些总结,感觉单纯在cdsn已经无法满足自己对这些东西的热爱,然后就开通了个人公众号,希望在公众号输出一些质量更好的总结文章。

        写了一两篇公众号里面的文章,自己会更加揣摩一句话该怎么写,怎么能把这个知识点简练的总结出来,欢迎关注个人公众号。

以上是关于Android 自定义Gradle插件的完整流程的主要内容,如果未能解决你的问题,请参考以下文章

Android 自定义Gradle插件的多层属性扩展

Android 自定义Gradle插件的多层属性扩展

Android Gradle 插件自定义 Gradle 插件模块 ⑤ ( 完整总结 ) ★★★

Android Gradle 插件Gradle 自定义 Plugin 插件 ⑥ ( 在 buildSrc 模块中依赖 Android Gradle 插件 | 完整代码示例 )

Android Gradle 插件Gradle 自定义 Plugin 插件 ⑥ ( 在 buildSrc 模块中依赖 Android Gradle 插件 | 完整代码示例 )

Android Gradle 中的Transform