对Gradle的疑问
从刚学习Android开始,就对gradle有着好多疑问,随着了解的越来越多,疑问也越来越多。疑问基本包括:gradle的作用是什么?gradle是怎么构建Android项目的?gradle每个文件的作用?gradle文件中配置具体是配置啥,都是什么含义?怎么使用gradle?多模块项目是怎么构建的?怎么在多模块中找到初始Activity?多模块之间怎么通信?多模块中每个模块要怎么单独运行?
抱着上面的疑问,开启对Gradle的研究。。。
Gradle在Android中的作用
- 作用:Gradle是一种构建工具,它可以帮你管理项目中的差异,依赖,编译,打包,部署......,你可以定义满足自己需要的构建逻辑,写入到build.gradle中供日后复用.
- Gradle脚本是基于Groovy语言来编译执行的(Groovy 是一种 JVM 上的脚本语言,基于 java 扩展的动态语言)
- 总结:一个可编程的工具,用来执行一系列有序的任务来表达自动化需求(编译源代码 -> 拷贝文件 -> 组装构建,当然这些任务不是固定的,可能还有其他任务)
- 再简单一点:Gradle就是自动化的编译、打包程序
- Gradle官网文档
- gradle学习系列文章
- Android Gradle 完整指南
Groovy的简单介绍:
本地只简单介绍一下 Groovy 在 Android 中的使用,要想深入了解需看gradle学习系列文章
Task:在Gradle中一个原子性的操作叫做 task,简单理解为task是Gradle脚本中的最小可执行单元。但是要是执行gradle文件中某个task,会发现所有task都被执行
Task Actions:一个 Task 是由一序列 Action (动作)组成的,当运行一个 Task 的时候,这个 Task 里的 Action 序列会按顺序依次执行。Gralde 里通过 doFirst、doLast 来为 Task 增加 Action 。doFirst:task执行时最先执行的操作doLast:task执行时最后执行的操作
Project:每一个 build.gradle 脚本文件被 Gradle 加载解析后,都会对应生成一个 Project 对象,在脚本中的配置方法其实都对应着 Project 中的API
-
Extension:Android中 gradle脚本中添加的类似android这种命名空间的配置就是extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容
- 嵌套Extension:Extension可以嵌套,类似于内部类的写法,在Android项目的gradle中随处可见
-
Android中的Extension:
-
NamedDomainObjectContainer:顾名思义就是命名领域对象容器,它的主要功能有:
- 它能够通过DSL(在Gradle脚本中)创建指定 type 的对象实例;
- 指定的 type 必须有一个 public 构造函数,且必须带有一个 String name 的参数,type 类型的领域对象必须有名为“name”的属性;
- 它是一个实现了 SortedSet 接口的容器,所以所有领域对象的 name 属性值都必须是唯一的,在容器内部会用 name 属性来排序;
Gradle在Android中的运用
Gradle的几个核心类型(主要从Android项目的角度)
Project:表示需要构建的一个项目,类似Android项目的一个module,Project和build.gradle是一一对应的。Project提供了一套api用来查看工程信息。
Task:对一个项目的构建,就是对该项目中Task集合的运行,每个Task执行一些基本的工作,比如:编译、运行单元测试、压缩成apk等
build.gradle:构建脚本(相当于maven的pom.xml)
gradle.properties:属性文件,每个项目可以创建属性文件也可以不创建。
setting.gradle:声明所需的配置来实例化项目的层次结构(意思就类似编译整个大工程,需要包含哪些模块一起编译)
Android Studio通过Gradle构建项目的文件介绍
以上文件结构表明:Android项目是Gradle通过多模块构建的
各文件作用介绍:
-
settings.gradleGradle:使用该文件来配置多项目构建。它应该由一行代码组成:
include ':app'
这告诉Gradle app子目录也是一个Gradle项目。如果在稍后的时间,您要通过可用的向导将Android库添加到此项目中,则会创建另一个项目子目录并将其添加到此文件中。- 我们在 Android 应用开发中,一个 Project 可以包含若干个 module ,这种就叫做多项目构建。在 Android Studio 项目中,根目录都有一个名叫 settings.gradle 的文件,然后每个 module 的根目录中又有一个 build.gradle 文件,Gradle 就是通过 settings.gradle 来进行多项目构建的。
- Gradle 在运行时会读取并解析 settings.gradle 文件,生成一个 Settings对象,然后从中读取并解析子项目的 build.gradle 文件,然后为每个 build.gradle 文件生成一个 Project 对象,进而组装一个多项目构建出来。
- Settings 里最核心的API就是 include 方法,通过该方法引入需要构建的子项目。
gradle-wrapper.properties:它配置了所谓的Gradle Wrapper。这使您可以构建Android项目,而无需首先安装Gradle,其一般结构如下:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
前四行表示当包装首次运行时,它将下载Gradle发行版并将其存储在主目录.gradle/wrapper/dists中的目录中。最后一行distributionUrl是Gradle的下载地址
- gradle.propertie 文件:
- 在与 build.gradle 文件同级目录下,定义一个名为 gradle.properties 文件,里面定义的键值对,可以在 Project 中直接访问:
gradle.properties里定义属性值:company="hangzhouheima"
在 build.gradle 文件里可以这样直接访问:println company = ${company}
- 扩展属性:可以通过 ext 命名空间来定义属性,我们称之为扩展属性:
定义属性:ext {username = "hjy" age = 30}
使用属性:println username
- 在与 build.gradle 文件同级目录下,定义一个名为 gradle.properties 文件,里面定义的键值对,可以在 Project 中直接访问:
- build.gradle:史上最全Android build.gradle配置详解
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { //这里是gradle脚本执行所需依赖,分别是对应的maven库和插件
repositories {
google() //从Android Studio3.0后新增了google()配置,可以引用google上的开源项目
jcenter() //是一个类似于github的代码托管仓库,声明了jcenter()配置,可以轻松引用 jcenter上的开源项目
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0' //此处是android的插件gradle,gradle是一个强大的项目构建工具
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}}
allprojects { //这里是项目本身需要的依赖,比如项目所需的maven库
repositories {
google()
jcenter()
}}
// 运行gradle clean时,执行此处定义的task任务。
// 该任务继承自Delete,删除根目录中的build目录。
// 相当于执行Delete.delete(rootProject.buildDir)。
// gradle使用groovy语言,调用method时可以不用加()。
task clean(type: Delete) {
delete rootProject.buildDir
}
方法解析:
1. apply:apply(options: Map
我们通过该方法使用插件或者是其他脚本,options里主要选项有:
- from: 使用其他脚本,值可以为 Project.uri(Object) 支持的路径
- plugin:使用其他插件,值可以为插件id或者是插件的具体实现类例如:
//使用插件,com.android.application 就是插件id
apply plugin: 'com.android.application'
//使用插件,MyPluginImpl 就是一个Plugin接口的实现类
apply plugin: MyPluginImpl
//引用其他gradle脚本,push.gradle就是另外一个gradle脚本文件
apply from: './push.gradle'
文件中第一行使用apply plugin表示应用了一个插件,其中提供了Android 编译、测试、打包等等的所有task,该插件一般有两种值可选:
-
com.android.application
:表示该模块为应用程序模块,可以直接运行,打包得到的是.apk文件 -
com.android.library
:表示该模块为库模块,只能作为代码库依附于别的应用程序模块来运行,打包得到的是.aar文件
2. 对Project进行配置:
//通过path定位并获取该 Project 对象
project(path: String): Project
//通过path定位一个Project,并进行配置
project(path: String, config: Closure): Project
//针对所有项目进行配置,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。
allprojects(config: Closure)
//针对所有子项目进行配置
subprojects(config: Closure)
3. 其中几个 {...} 闭包 解释下:
buildscript {...} --> 定义了 Android 编译工具(gradle)的类路径。声明仓库、添加插件专用闭包。就是对gradle的配置
repositories {...} --> 就是代码仓库,我们平时的添加的一些 dependency 就是从这里下载的。 jCenter是一个著名的 Maven 仓库。
dependencies {...} --> 添加插件、远程依赖
apply --> 使用插件,插件虽然导入进来了,但是子项目用不用就是个事了,要是不用的话,是不需要打包进来的,所以要主动声明下
每个项目单独的 build.gradle --> 针对每个moudle 的配置,如果这里的定义的选项和顶层build.gradle定义的相同,顶层build.gradle会被覆盖
4. 依赖的添加格式: group:name:version(组名:库名称:版本号)
举例:
implementation 'androidx.appcompat:appcompat:1.2.0'
本地依赖:通过files()方法可以添加文件依赖,如果有很多jar文件,我们也可以通过fileTree()方法添加一个文件夹,除此之外,我们还可以通过通配符的方式添加:implementation fileTree(dir: 'libs', include: ['*.jar'])
每个项目单独的 build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.wang.text"
minSdkVersion 23
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation project(path: ':module_two'
}
- apply plugin:第一行代码应用了Android 程序的gradle插件,作为Android 的应用程序,这一步是必须的,因为plugin中提供了Android 编译、测试、打包等等的所有task。
- android:这是编译文件中最大的代码块,关于android 的所有特殊配置都在这里,这就是又我们前面的声明的 plugin 提供的。
- defaultConfig就是程序的默认配置,注意,如果在AndroidMainfest.xml里面定义了与这里相同的属性,会以这里的为主。
- 这里最有必要要说明的是applicationId的选项:在我们曾经定义的AndroidManifest.xml中,那里定义的包名有两个用途:一个是作为程序的唯一识别ID,防止在同一手机装两个一样的程序;另一个就是作为我们R资源类的包名。在以前我们修改这个ID会导致所有用引用R资源类的地方都要修改。但是现在我们如果修改applicationId只会修改当前程序的ID,而不会去修改源码中资源文件的引用。
- buildTypes:定义了编译类型,针对每个类型我们可以有不同的编译配置,不同的编译配置对应的有不同的编译命令。默认的有debug、release 的类型。
- dependencies:是属于gradle 的依赖配置。它定义了当前项目需要依赖的其他库。
Module完整的build.gradle配置如下
// 声明是Android程序,//com.android.application 表示这是一个应用程序模块//com.android.library 标识这是一个库模块//而这区别:前者可以直接运行,后着是依附别的应用程序运行
apply plugin: 'com.android.application'
android {
signingConfigs {// 自动化打包配置,签名
release {// 线上环境
keyAlias 'test'
keyPassword '123456'
storeFile file('test.jks')
storePassword '123456'
}
debug {// 开发环境
keyAlias 'test'
keyPassword '123456'
storeFile file('test.jks')
storePassword '123456'
}
}
compileSdkVersion 27//设置编译时用的Android版本
defaultConfig {
applicationId "com.billy.myapplication"//项目的包名
minSdkVersion 16//项目最低兼容的版本
targetSdkVersion 27//项目的目标版本
versionCode 1//版本号
versionName "1.0"//版本名称
flavorDimensions "versionCode"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"//表明要使用AndroidJUnitRunner进行单元测试
}
buildTypes {// 生产/测试环境配置
release {// 生产环境
buildConfigField("boolean", "LOG_DEBUG", "false")//配置Log日志
buildConfigField("String", "URL_PERFIX", "\"https://release.cn/\"")// 配置URL前缀
minifyEnabled false//是否对代码进行混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//指定混淆的规则文件
signingConfig signingConfigs.release//设置签名信息
pseudoLocalesEnabled false//是否在APK中生成伪语言环境,帮助国际化的东西,一般使用的不多
zipAlignEnabled true//是否对APK包执行ZIP对齐优化,减小zip体积,增加运行效率
applicationIdSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
versionNameSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
}
debug {// 测试环境
buildConfigField("boolean", "LOG_DEBUG", "true")//配置Log日志
buildConfigField("String", "URL_PERFIX", "\"https://test.com/\"")// 配置URL前缀
minifyEnabled false//是否对代码进行混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//指定混淆的规则文件
signingConfig signingConfigs.debug//设置签名信息
debuggable false//是否支持断点调试
jniDebuggable false//是否可以调试NDK代码
renderscriptDebuggable false//是否开启渲染脚本就是一些c写的渲染方法
zipAlignEnabled true//是否对APK包执行ZIP对齐优化,减小zip体积,增加运行效率
pseudoLocalesEnabled false//是否在APK中生成伪语言环境,帮助国际化的东西,一般使用的不多
applicationIdSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
versionNameSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
}
}
sourceSets {//目录指向配置
main {
jniLibs.srcDirs = ['libs']//指定lib库目录
}
}
packagingOptions{//打包时的相关配置
//pickFirsts做用是 当有重复文件时 打包会报错 这样配置会使用第一个匹配的文件打包进入apk
// 表示当apk中有重复的META-INF目录下有重复的LICENSE文件时 只用第一个 这样打包就不会报错
pickFirsts = ['META-INF/LICENSE']
//merges何必 当出现重复文件时 合并重复的文件 然后打包入apk
//这个是有默认值得 merges = [] 这样会把默默认值去掉 所以我们用下面这种方式 在默认值后添加
merge 'META-INF/LICENSE'
//这个是在同时使用butterknife、dagger2做的一个处理。同理,遇到类似的问题,只要根据gradle的提示,做类似处理即可。
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
productFlavors {
wandoujia {}
xiaomi {}
_360 {}
}
productFlavors.all {
//批量修改,类似一个循序遍历
flavor -> flavor.manifestPlaceholders = [IFLYTEK_CHANNEL: name]
}
//程序在编译的时候会检查lint,有任何错误提示会停止build,我们可以关闭这个开关
lintOptions {
abortOnError false
//即使报错也不会停止打包
checkReleaseBuilds false
//打包release版本的时候进行检测
}
}
dependencies {
//项目的依赖关系
implementation fileTree(include: ['*.jar'], dir: 'libs')
//本地jar包依赖
implementation 'com.android.support:appcompat-v7:27.1.1'
//远程依赖
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
//声明测试用例库
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'}
Gradle 构建生命周期
在解析 Gradle 的编译过程之前我们需要理解在 Gradle 中非常重要的两个对象。Project和Task。
每个项目的编译至少有一个 Project,一个 build.gradle就代表一个project,每个project里面包含了多个task,task 里面又包含很多action,action是一个代码块,里面包含了需要被执行的代码。
在编译过程中, Gradle 会根据 build 相关文件,聚合所有的project和task,执行task 中的 action。因为 build.gradle文件中的task非常多,先执行哪个后执行那个需要一种逻辑来保证。这种逻辑就是依赖逻辑,几乎所有的Task 都需要依赖其他 task 来执行,没有被依赖的task 会首先被执行。所以到最后所有的 Task 会构成一个 有向无环图(DAG Directed Acyclic Graph)的数据结构。
无论什么时候执行Gradle构建,都会运行3个不同的生命周期阶段:初始化、配置、执行。
- 初始化(Initialization):Gradle为每个项目创建一个Project实例,在多项目构建中,Gradle会找出哪些项目依赖需要参与到构建中。
- 配置(Configuration):执行所有项目的构建脚本,也就是执行每个项目的build.gradle文件。这里需要注意的是,task里的配置代码也会在这个阶段执行。
- 执行(Execution):Gradle按照依赖顺序依次执行task。
自己总结出来的gradle多模块构建流程:(若有错误还望指出)
项目启动编译后,会先下载gradle-wrapper.properties文件中配置的gradle版本,若已经下载就不需要下载了;然后去加载Setting文件,将其中指定的所有module全列入编译;最后从根gradle文件开始配置,编译。
引用博客:
gradle学习系列文章
史上最全Android build.gradle配置详解
Gradle在Android工程中的运用
重要-作为Android开发者必须了解的Gradle知识
Android Gradle 完整指南