gradle入门 (指南介绍)

本文深入讲解Gradle构建原理,涵盖Gradle脚本结构、Gradle生命周期、插件编写及产品口味配置等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Gradle是一门基于groovy的DSL(特定领域语言)语言,也就是说它只能在某一特定领域使用,而不通用。
学习Gradle主要需要掌握下面三种语言
1 、Groovy语言
2 、Gradle DSL
3 、Android DSL
我们前面说了Gradle是一门基于groovy的DSL,可能很多童鞋会对这个有迷惑,第一感觉就是Gradle是一门独立的语言呀,如果你这么想就“误入歧途“了,因为我一开始也是这么迷糊的,当你了解过后,你就可以这么理解Gradle就是用groovy语言实现的一个框架,我们基于这个框架可以方便的去写构建脚本。
由于Android Studio已经采用并内嵌了Gradle作为构建脚本,所以我们简单的看下AS中的Gradle结构。
我们在AS中新建一个项目都会有这个目录
这里写图片描述
这个是什么呢?这个就是所谓的Gradle包装器了,这个4.1 呢就是Gradle的版本号,你想想框架也需要不断升级增加或者废弃掉一些功能呀,所以这个版本号越新表示表示Gradle的语法越新。
这个版本号我们怎么控制呢?
这里写图片描述
打开gradle-wrapper.properties就可以在箭头处去指定修改Gradle版本号
这里写图片描述

我们也可以在在File选项中进行配置
这里写图片描述

这里写图片描述

在Android Studio中整个项目结构是一个Project,里面的android应用以及library目录都是module,Project结构下主要如下:

.gitignore文件 :这个是git的忽略配置文件
build.gradle : 这个是整个工程的gradle脚本配置,这里面脚本内容会在所有的module上生效
gradle.properties : 这个文件可以配置jvm虚拟机一些参数,也可以调整设置内存大小。
local.properties :这个文件是配置sdk、ndk目录路径的,这个路径根据个人主机环境变量自动生成,所以我们需要把它添加到gitgore忽略文件中。
settings.gradle :这里面配置我们整个工程所有需要依赖到的module,包括android应用以及依赖到的library

AS首先执行Project的build.gradle脚本进行构建,这个脚本中所有的配置对所有module生效,接着会去执行setting.gradle脚本,首先去执行apply plugin: ‘com.android.application’ 应用目录下的build.gradle脚本进行构建,接着依次执行其他module下的build.gradle脚本,这就是AS主要工作流程。

我们打开Project下的build.gradle看下结构内容
这里写图片描述

这些都是Gradle DSL语法进行配置,dependencies 下面依赖的就是Android Gradle插件,这里我们必须添加,这个版本号跟上面Gradle版本号有相互对应关系,因为插件是基于Gradle版本写的,太高的Gradle版本对应太低的Gradle版本亦或相反都不支持,这里贴张官网的图看下就明白了。

https://developer.android.com/studio/releases/gradle-plugin.html
这里写图片描述

到此为止,我们对Android Studio中的gradle脚本结构差不多了解了,接下来我们了解下gradle语法

每个build.gradle脚本就是一个Project,一个项目工程由若干个Project构建而成,而一个Project又是由若干个Task组成,每个Task又会执行若干个Action, 执行setting.gradle脚本时,会为里面每个目录下的build.gradle创建一个Project实例。

这里写图片描述

上图我们配置dependencies dsl实际就是调用Project对象的方法. 而Android标签下的dsl属于Android DSL,Android DSL提供特定配置Android Studio项目工程的一些api,它不属于Gradle Api。
Gradle DSL Api可以查阅文档:
https://docs.gradle.org/current/dsl/org.gradle.api.Project.html

Android DSL具体Api可以查阅文档:
http://google.github.io/android-gradle-dsl/current/index.html#N10009

我们前面说了一个Project是由若干个Task组成,每个 Task 都代表了构建执行过程中的一个原子性操作(如编译,打包,生成 javadoc,发布到某个仓库等操作),我们随便打开一个项目工程看下task究竟是什么
这里写图片描述

可以看到,build目录下存放的是我们构建时需要在我们在执行gradlew build命令时,会依次按顺序执行build下面的Task,我们也可以配置自己的Task.由于没有安装工具,这么就直接在gradle脚本文件中测试。

这里写图片描述

Task申明语法是 task xxx { }
这里我申明了一个名称为task1的task,我创建了一个Map,Map中只有一个简单的键值对,然后在task1中遍历这个Map然后分别打印出key和value值,代码就是这么简单我们执行build命令看下结果。

这里写图片描述

可以清楚的看到已经打印出了key value值,如果你热爱思考的话你可能会疑问为什么执行build命令时会执行我们的task打印结果,我们明明没有执行task1呀。
这就需要了解gradle的2个生命周期 配置阶段 、执行阶段

1 .配置阶段

上面task1中代码就属于配置阶段,在Project执行任何task任务前会执行这个Project中所有配置代码。我们上面执行build命令实际就是执行一堆task进行构建,构建前会执行配置代码

2. 执行阶段

执行阶段的代码就是一个个的Action,doLast是Gradle提供的Api, 举个例子上面同样代码配置到执行阶段可以这样写
这里写图片描述
我们将配置阶段代码包裹在doLast中这样就会将代码从配置阶段转换为执行阶段了,我们再执行下build命令试一下
这里写图片描述
结果可以看到只执行打印了配置阶段的代码,而我们放在执行阶段里的Map并没有被打印,现在我们执行这个task1在看下结果
这里写图片描述

这里为了测试我在task1上面的android标签里加了段配置代码所以从打印结果可以看到,我们执行task之前会先依次执行所有task里的配置,然后才去执行我们task里的Action

我们点击doLast源码看看
这里写图片描述

可以看到实际就是把我们{ }整个闭包作为参数传递给doLast参数,然后转化成一个Action对象存入ArrayList集合,所以我们执行task实际就是获取到这个集合并遍历然后调用每个Action执行execute(T t)方法最终会调用闭包Closure的call()方法。

在application app的build.gradle中,我们会发现有这么一句代码

apply plugin: 'com.android.application'  

我们前面说了这段代码的意思是应用安卓插件申明这个build.gradle所属的项目是android应用,如果要想申明为依赖库的话就要应用library插件

apply plugin: 'com.android.library'  

当然我们也可以去应用自己写的插件,gradle提供给我们Plugin接口用于扩展自己的插件,方式很简单只要编写xxx.grovvy文件实现Plugin接口就好,例如:

class ApkPlugin implements Plugin<Project>{
    @Override
    void apply(Project target) {
         target.task("test1")<<{
             println "test1"
         }
    }
}

我们这里创建了一个ApkPlugin插件,代码也很简单,Plugin接口中有个apply方法必须实现,Plugin接口有个泛型,这个泛型就是我们插件所应用的脚本类型,它会作为一个对象传递到apply方法中,比如我们在build.gradle脚本中应用我们的插件,build.gradle整体就是一个Project对象,所以我们这里泛型就申明为Project.

当我们在build.gradle中增加apply plugin: ApkPlugin应用我们写的插件后,整个build.gradle会作为一个Project参数传递给apply方法,上面代码很简单,就是调用Project的task(String taskName)参数方法去创建一个task并应用到Project对象中,然后我们在命令中执行下这个task看下结果

这里写图片描述
可以看到我们已经成功为这个project添加了task并执行成功。

我们在build.gradle中经常可以看到大量这样的语句
这里写图片描述
圆圈处是我们对Android工程进行一些常规的属性配置,可能看到这你会很迷糊,我知道要这样写,可是为什么非要这样写呢,这里需要了解Extension(扩展),因为在gradle脚本中,这种思想被大量运用。

我们这么想,如果官方提供的插件不满足我们需求,那么我们需要编写自己的插件,上面为了测试方便直接把插件写到了build.gradle脚本当中,但实际上我们最好不要这么干,因为代码需要复用,如果我把插件做成依赖提供给别人使用的话,那么我们在插件内部代码中怎么获取到外部配置的值呢?

我们还以上面编写的ApkPlugin为例。

这里写图片描述
但是你会发现这里代码稍微有些变动,在插件里创建task之前调用了这么一句

 project.getExtensions().create("ApkDistExtension",ApkDistExtension)

getExtensions()方法点进去可以看到得到的是一个ExtensionContainer对象。
这里写图片描述

这里写图片描述
看文档介绍这个接口允许将DSL扩展命名空间添加到一个指定对象中,这个接口可以创建我们指定需要扩展配置的类型对象,并且给这个对象设置一个额外的命名空间,然后将创建的对象赋值为调用这个接口方法对象的成员变量中。

在上面例子中我们创建了一个ApkDistExtension类,里面有个versionName String类型的成员变量,初始值是null,在创建task之前,我们调用ExtensionContainer对象的create方法创建了一个ApkDistExtension对象并且给这个对象命名空间设置为“ApkDistExtension”,这里也可以任起其他命名,然后把创建的这个对象赋值成project对象里的一个成员变量。

然后在build.gradle中我们可以通过我们设置的命名空间进行配置

ApkDistExtension{
  versionName "v1.0.1"
}

在apply方法中就可以这样获取到我们配置的值

project["ApkDistExtension"].versionName

这里写图片描述
执行task1可以打印获取到我们配置的值,看到这你应该就明白上面android项目为什么要那么配置了。

在国内有众多应用市场,我们如果针对不通市场打包不同应用分别投放的话,gradle给我提供了productFlavors这个便捷的DSL.
我们可以把公共的参数配置放在默认配置 defaultConfig{ }中,然后在android标签内利用productFlavors进行特有的风味配置 例如:
这里写图片描述

这里写图片描述

这里我分别配置了3个版本 HuaWei、XiaoMi、MeiZu,当然你也可以配置更多版本,这里面每个版本都会与defaultConfig进行合并如果有重复的配置,productFlavors中版本的配置会覆盖掉defaultConfig中的配置,productFlavors中每个版本会跟defaultConfig合并然后分别跟buildTypes中每个版本合并生成一个最终版本的apk,比如上面例子中合并规则会这样
HuaWei配置->(合并)defaultConfig 配置->(合并)buildTypes release配置
HuaWei配置->(合并)defaultConfig 配置->(合并)buildTypes debug配置

XiaoMi配置->(合并)defaultConfig 配置->(合并)buildTypes release配置
XiaoMi配置->(合并)defaultConfig 配置->(合并)buildTypes debug配置

MeiZu配置->(合并)defaultConfig 配置->(合并)buildTypes release配置
MeiZu配置->(合并)defaultConfig 配置->(合并)buildTypes debug配置

如果我们需要统计渠道的话我们可以在AndroidManifest.xml文件下的application标签内添加一个meta-data

<meta-data android:name="CHANNEL_VALUE"   
         android:value="${channel_value}"/>

这里value我们不写死,用个变量代替
这里写图片描述
这里我们又使用了一个android插件提供的 manifestPlaceholders DSL
productFlavors中这么配置之后,我们打包apk时就可以把不同版本配置中channel_value的值替换到AndroidManifest哪个变量里

然后在启动Activity中我们就可以这样写获取到不同版本的渠道值

    public static String getAppMetaData(Context ctx, String key) {
        if (ctx == null || TextUtils.isEmpty(key)) {
            return null;
        }
        String resultData = null;
        try {
            PackageManager packageManager = ctx.getPackageManager();
            if (packageManager != null) {
                ApplicationInfo applicationInfo = packageManager.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA);
                if (applicationInfo != null) {
                    if (applicationInfo.metaData != null) {
                        resultData = applicationInfo.metaData.getString(key);
                    }
                }

            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        return resultData;
    }

封装这么个方法后直接调用getAppMetaData(this,”CHANNEL_VALUE”)就可以获取到我们不同渠道的值了,
我们看下不同版本打印的渠道值
HuaWei:
这里写图片描述

MeiZu:
这里写图片描述

XiaoMi:
这里写图片描述

可以看到打印出的分别是不同版本获取到的渠道,是不是美滋滋~

applicationVariants

这个是android插件给我们提供的一个属性,它有什么作用呢?
我们前面说过,利用productFlavors可以设置多个不同的版本,这些版本还会跟defaultConfig以及buildTypes组成多个不同的版本,这些不同的版本就是Build Variants(构建变体),如果我们想利用代码动态的去控制改变这些构建变体信息的话就可以利用这个属性。

这里写图片描述

比如上面我创建了2个风格版本,我们想打包的时候,打包输出apk名前缀对应版本名怎么办呢?利用applicationVariants很容易实现我们看代码
这里写图片描述

代码很简单,这里遍历整个Project的构建变体,记住这里要用all去遍历否则不生效,然后遍历每个构建变体的所有outputs输出,然后把每个构建变体版本名赋值给每个构建变体输出File,这样打包后就会输出不同版本名的apk

注意由于Android插件3.0后更改,修改输出文件名不要用下面这种写法了否则会编译报错
这里写图片描述

这里写图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值