《在飞Android Gradle实战》之核心模块Plugin4

hi各位小伙伴,上一章《在飞Android Gradle实战》之核心模块Task3 介绍了Gradle核心模块Task的相关内容。

      Task 是真正执行逻辑的角色,大到源码编译,小到copy功能,最后执行者都是task。重点讲了task的创建、查找、以及如何指定task的输入输出、执行顺序、它的依赖。而通过指定task的执行顺序和依赖,可以将我们自己的task插入到一些构建过程中去,可以完成特定的功能等等。

前言:

  了解完核心Task,还需要了解另一个核心内容就是Plugin,同时如何来自定义一个Plugin插件。

  还是一样的风格,只讲实战中经常用到的内容,让你快速入门和使用,加上我自己的分析让你更加清楚的了解这些内容。至于基础知识请自己学习~。

本章难度:简单  普通 困难    

本章重要程度:普通 重要 核心

这章主要说下Gradle自定义Plugin的相关实战经验。主要内容还有Gradle Setting、SourceSet、自定义Plugin、Android插件对Plguin的扩展等等,大体内容如下:

一:Settings

    Android开发中可能不大使用到Settings这个类,但是settings.gradle这个文件作为Andorid Rd我们会经常看到。他俩有什么关系呢?

    Settings这个类如果要被gradle初始化,完全是settings.gradle中的配置内容的功能。完成对Settings类的初始化首先是配置settings.gradle文件。Settings类的核心作用是决定我们工程中哪些module是要被gradle处理的。

include ':ToolboxSample'

     include():引入子工程名字叫ToolboxSample,只有加入了这句话我们项目中ToolboxSample文件夹才会被gradle当成一个工程去处理。

经验:

     Settings类,对于开发人员来说是简单的,我们一般只需要配置内部的include。但是它的作用并不简单,因为它自己就占用了一个Gradle的生命周期那就是初始化阶段。所以我们说的gradle的初始化阶段,完全就是在执行settings.gradle文件中的内容。通过settings.gradle中的内容决定我们到底有多少子工程需要被我们处理。

二:SourceSet

为什么我们的gradle知道从我们的src/main/java目录去读取我们的源码呢?就是通过我们的sourceSet这个类做到的。

     SourceSet这个类中决定了我们所有源码、资源、第三方库等要存放的位置。那我们创建新项目的时候并没有指定SrouceSet的目录,那它是怎么拿到的呢?这是因为Gradle内部的规则 ‘约定>配置’,就是说只要我们没有主动做修改那么优先默认配置。 Gradle默认就是从java目录下获取源码、从res目录下获取资源进行编译。所以这就是我们SourceSet的关键作用,就是管理我们的源码、资源、库等存放的位置。

     AndroidSourceSet:因为我是android开发人员,所以我们看的是AndroidSourceSet,如果你是java开发人员,那么就是JavaSourceSet的源码。

   通过看源码看到里面有java、mainfest、res、assets、jni、jniLibs等设置方法,说明这些内容文件夹我们都是可以去修改的。

举例:

     1.修改jnilib下的so库。使用app->libs文件夹下寻找so文件,而不是在默认的jnilibs下面。

     2.将我们的res文件也分包来管理。


    sourceSets{
        main{
            jniLibs.srcDirs=['libs']//1.修改so库的存放位置改为libs文件夹
        }
        //2.res分包管理
        main {//同理也可以设置资源文件,添加 res-ad,res-player。
            res.srcDirs = ['src/main/res', 'src/main/res-ad', 'src/main/res-player']
        }
    }

经验:

    SourceSets是可以调动多次的,所以上面了例子你也可以分两个SourceSets来分别处理不同的功能。SourceSets不只是在android{}闭包中可以使用,在project中也是可以进行配置的,如下:

this.sourceSets {
    main{
        jniLibs.srcDirs=['libs']//修改so库的存放位置改为libs文件夹
    }
}

三:Plugin

     Plugin可以将完成任务的所有task封装在一个Plugin中,供其他使用。比如android 插件,可以打成aar、apk等。

如何创建插件?

    举例:

1.创建 自定义的名称buildSrc工程:gradle会将buildSrc变成成一个插件,内部结构与普通应用程序是一样的。但是在main中是groovy,因为我们开发plugin都是创建的groovy类。

2.build.gradle:配置sourceSet

  

   3.创建resources文件夹 

   4.创建类GradleStudyPlugin1实现Plugin接口,实现apply()

   

class GradleStudyPlugin1 implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println 'Hello plugi....'+project.name
        //创建扩展属性
        project.extensions.create('dapReleaseInfo1',
                ReleaseInfoExtension1)
        //创建Task
        project.tasks.create('dapReleaseInfoTask1',
                ReleaseInfoTask1)

    }
}

经验:    

     GradleStudyPlugin1其实就是一个自定义插件了,apply()中参数project,就是引入了当前这个自定义插件对应的project。

5.声明别人如何使用我们的插件

     别人如何引用我们的插件呢:就是com.dap.gradle.study.property这个文件中的‘com.dap.gradle.study’

6.App中使用自定义插件

     

SyncNow之后看控制台

看Android Studio的Terminal,如果打印出你自定义的plugin中的内容,那么证明你的自定义Plugin运行成功

四:自定义插件实战:将之前我们的写、读xml的两个Task功能封装成一个自定义插件     

/**
 * 与自定义PLugin进行参数传递
 */
class ReleaseInfoExtension1 {
    String versionCode
    String versionName
    String versionInfo
    String fileName
    ReleaseInfoExtension1(){}

    @Override
    String toString() {
        """| versionCode = ${versionCode}
           | versionName = ${versionName}
           | versionInfo = ${versionInfo}
           | fileName = ${fileName}
        """.stripMargin()
    }
}

     此类与java中的bean(实体类)同理。

class GradleStudyPlugin1 implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println 'Hello plugi....'+project.name
        //创建扩展属性
        project.extensions.create('dapReleaseInfo1',
                ReleaseInfoExtension1)
        //创建Task
        project.tasks.create('dapReleaseInfoTask1',
                ReleaseInfoTask1)

    }
}

使用project.extensions.crate('')创建,第一个参数:key   第二个参数:就是有哪些value值,使用ReleaseInfoExtension1中的变量。这样外部就可以使用dapReleaseInfo1这个闭包来完成RelaseInfoExtension1这个类的初始化。

App下面的build.gradle使用,设置参数及数据。

下一步自定义一个Task,里面实现我们的write、read具体业务。

//自定义Task
class ReleaseInfoTask1 extends DefaultTask {
    ReleaseInfoTask1() {
        group = 'dap'
        description = 'update the release info'
    }

    @TaskAction
    void doAction() {
        updateInfo()
    }
//将extension类信息写入指定文件中
    private void updateInfo() {
        //获取将要写入的信息
        String versionCodeMsg = project.extensions.
                dapReleaseInfo1.versionCode
        String versionNameMsg = project.extensions.
                dapReleaseInfo1.versionName
        String versionInfoMsg = project.extensions.
                dapReleaseInfo1.versionInfo
        String fileName = project.extensions.
                dapReleaseInfo1.fileName
        def file = project.file(fileName)
        //将实体对象写入到xml文件中
        def sw = new StringWriter()
        def xmlBuilder = new MarkupBuilder(sw)
        if (file.text != null && file.text.size() <= 0) {
            //没有内容
            xmlBuilder.releases {
                release {
                    versionCode(versionCodeMsg)
                    versionName(versionNameMsg)
                    versionInfo(versionInfoMsg)
                }
            }
            //直接写入
            file.withWriter { writer -> writer.append(sw.toString())
            }
        } else {
            //已有其它版本内容
            xmlBuilder.release {
                versionCode(versionCodeMsg)
                versionName(versionNameMsg)
                versionInfo(versionInfoMsg)
            }
            //插入到最后一行前面
            def lines = file.readLines()
            def lengths = lines.size() - 1
            file.withWriter { writer ->
                lines.eachWithIndex { line, index ->
                    if (index != lengths) {
                        writer.append(line + '\r\n')
                    } else if (index == lengths) {
                        writer.append('\r\r\n' + sw.toString() + '\r\n')
                        writer.append(lines.get(lengths))
                    }
                }
            }
        }

    }


}

运行

经验:

    为什么继承DefaultTask?因为我们所有task默认是继承这个Task的。这个Task里的内容就是我们的核心业务逻辑,实现版本信息的维护。@TaskAction被这个注解的方法,可以将这个方法放到我们的执行阶段去执行类似doLast{}闭包。

    updateInfo()中是我们的业务逻辑。具体内容可以关注前面的Task篇的内容 《在飞Android Gradle实战》之核心模块Task3。

五:Android插件扩展

   Android对Gradle的扩展功能很多,只说一些我用到过的一些内容。

   this.android{}这个闭包中就是android提供的扩展内容,具体提供的扩展方法,可以在BaseExtension这个类中查看。

这里面的属性都可以在android{}闭包中使用,具体API可以自己研究。

BaseVariant类中是Andorid插件提供的所有Task。

Android中有三种变体(Variant),分别是applicationVariant(只使用与app plugin)、libraryVariant(只适用于library plugin)、testVaraint

可参考:http://avatarqing.github.io/Gradle-Plugin-User-Guide-Chinese-Verision/advanced_build_customization/manipulation_taskstask.html

举例:name与baseName的区别

this.afterEvaluate {
    this.android.applicationVariants.all { variant ->
        def name = variant.name
        def baseName = variant.baseName
        println "android.applicationVariantsName:${name}"
        println "android.applicationVariantsBaseName:${baseName}"
        variant.outputs.all {
            def apkName = "app-${variant.baseName}-${variant.versionName}.apk"
            outputFileName = apkName
            println outputFileName
        }

        def manifest = variant.checkManifest
        println "manifest:${manifest.name}"
    }
}

经验:

    name结果是xxxDebug,而baseName结果是xxx-debug。

    其他方法,比如variant.buildType()获取buildType varinant.flavorName()获取flavor的名字  varinant.signingConfig()签名等等。所以说build.gradle中对 android{}的配置就是对变体(variant)的配置。

    varinat也可以获取到task例如variant.checkManaifet,获取到 结果checkXxxxDebugManifest这个Task。

gradle总结:

      gradle生命周期:

     只有了解了gradle的生命周期才能写出正确的脚本代码。初始化、配置、执行。初始化阶段gradle会完成所有工程的初始化,决定我们的项目有多少个子项目 这个阶段重点就是解析setting.gradle文件,初始化阶段完毕就是配置阶段,build.gradle中的代码大部分都是执行在配置阶段的,配置阶段执行完之后就是运行阶段,在 运行阶段段真正执行task中的逻辑,只有知道这三个阶段的作用,才能写出正确的脚本代码,否则代码很容易执行再错误的生命周期。可参考:

核心模块project:

     Project是脚本代码的入口,所有脚本代码都是写在project的实力中的,而每一个build.gradle文件就对应一个project类的实例,可以在 build.gradle文件中去定位文件 获取root工程以及管理子工程,为Project添加依赖等等。可参考:

核心模块Task:

     是真正执行逻辑的角色,大到源码编译,小到copy功能,最后执行者都是task。重点讲了task的创建、查找、以及如何指定task的输入输出、执行顺序、它的依赖。而通 过指定task的执行顺序和依赖,可以将我们自己的task插入到一些构建过程中去,可以完成特定的功能。可参考:

Gradle其他模块:

     包括settings、sourceset、plugin等,都是重要的。可参考:

gradle实战总结:

    版本信息维护功能的开发,帮我们熟悉了task的具体实现业务流程和熟悉代码api。

    告诉大家如何将自己的task挂接到生命周期的中,同时去完成我们自己的功能,

    如何自定义一个插件 ,plugin其实就是多个task的一个集合

Ps:我是在飞~~,只有空闲时间更新博客,所以本系列偏快速入门和个人经验分享。主要讲实战中经常用到和我认为重要的内容。所以文章尽量简短,敬请谅解,希望我的博客对你有帮助!本系列暂定阅读者已经有groovy基本知识,如果需要我来说下groovy内容也可以评论中提出,后续单开一章带领大家简单入门下Groovy。

gradle系列文章本章是最后一篇,谢谢大家的阅读,祝各位工作顺利!

年后到现在一个多月的时间紧紧巴巴的,但还是完成《gradle实战系列》。按照计划,后续将是《区块链系列》的实战、经验内容分享~

有问题可联系q:1660380990邮箱[email protected]
 

发布了72 篇原创文章 · 获赞 15 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/pangzaifei/article/details/87868601
今日推荐