Gradle - Android工程结构和编译相关介绍
什么是Gradle?Gradle是一个开源的,具备高度灵活性和高性能的自动化构建工具,最重要的是,它是Android官方构建工具,任何Android开发人员都绕不开它
对于第一次接触Gradle的Android开发人员来说,在新建一个工程后,会带着如下疑问
- Gradle Wrapper是干啥用的?
- 工作目录下的Gradle脚本的作用是啥?
- 整个构建流程是啥样的?
Gradle Wrapper
Gradle Wrapper顾名思义,是对Gradle包了一层,其主要目的是为了实现对Gradle指定版本的下载和缓存管理
Android工程内跟Gradle Wrapper相关的文件有
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
先看gradle-wrapper.properties的定义
#Mon Jul 29 15:41:15 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
文件里配置了gradle版本zip包保存位置和解压后的发布位置,默认都是
~/.gradle/wrapper/dists
还有就是distributionUrl了
wrapper目录下的gradle-wrapper.jar包含了gradle wrapper的具体实现
gradlew则是对gradle命令做了一层包装,主要是在真正的执行gradle构建前,完成gradle指定版本的下载和发布到本地缓存目录
distributionUrl不单单指定了gradle版本下载url,同时它也指定了gradlew构建时所使用的gradle版本,如果该版本在本地缓存目录已经存在,则直接使用,否则触发下载
构建流程
Android工程的构建流程大体如下:
步骤 | 描述 | 脚本 |
---|---|---|
1 | 初始化Gradle构建环境,创建Gradle对象 | init.gradle |
2 | 创建工程Root Project | 根目录build.gradle |
3 | 创建Setting,用于配置root project的关联子project | settings.gradle |
4 | 根据setting配置,依次创建子工程project | 子工程根目录build.gradle |
咱们可以这样理解,在上面的每一步骤,都会创建一个对象,而关联脚本DSL执行时所配置的目标就是这个对象,那既然目标是这个对象,脚本执行环境(delegate)必然是这个对象本身
第2,3,4步的gradle文件都是在目标工程目录下,第1步有点不同,gradle在默认情况下,不会读取工程内部的init.gradle, 只会读取GRADLE_HOME/init.gradle
~/.gradle/init.gradle
那如果要在工程内部指定类似init.gradle的DSL文件,需要编译时指定
./gradlew -I or --init-script [name].gradle
如果编译时指定了init.gradle脚本,则脚本的加载顺序是
- 编译时指定的gradle脚本
- GRADLE_HOME/init.gradle
还有,gradle在初始化时,默认还会按如下顺序读取gradle.properties配置工程的全局属性
- GRADLE_HOME/gradle.properties
- 工程根目录的gradle.properties
gradle源码查看
可以通过如下路径找到gradle对应版本的发布路径
~/.gradle/wrapper/dists
路径下面的src目录,即为源码
Gradle配置脚本介绍
上面说过,每个脚本执行时,它的执行delegate都是对应的构建对象,也就是说,我们可以在脚本里头调用对象的暴露的接口,从而完成想要的数据配置,如果对每个关联对象包含哪些接口不清楚,除了可以直接看官网介绍外,还可以将对象的metaClass打印出来,知道对应的实现类后,直接查看其源码
init.gradle
init.gradle对应Gradle对象,下面举个简单的例子,在工程根目录创建init.gradle
afterProject{ project ->
println "project ${project.getName()} is eavalated finish"
}
然后编译
./gradlew assembleDebug -I init.gradle
编译时,就会在每个module evaluated结束后,打印上述信息
build.gradle - root
root project的build.gradle在工程的根目录,对应对象Project,用于配置工程全局的数据,包括:
- gradle构建环境所需的依赖class path,对应buildscript{}
- 工程编译所需的全局配置,对应allprojects{}
setting.gradle
位于根目录的setting.gradle,对应对象Settings,用于配置root project包含的module projects
include ':app', ':mylibrary'
def libspath = rootProject.projectDir.getPath() + File.separator + 'Libraries/'
project(':mylibrary').projectDir = new File(libspath + 'mylibrary')
上面的这个是最简单的settings.gradle配置,也是大多数app的配置
我们看Settings接口的定义
public interface Settings extends PluginAware, ExtensionAware {
}
可以看出,Settings这个对象是支持plugin和extension的,所以,在settigns.gradle里头,除了做上面基础的配置外,它自身是支持apply plugin和创建自定义extension的
build.gradle - module
module project的build.gradle,这个是大家开发中接触最多的和改动的核心,它主要用于完成module project编译配置,主要包括:
- apply工程所需的各种gradle插件
- 配置各个插件创建的extension(如果有的话)
- 配置project dependencies
module project的build.gradle文件:
apply plugin: 'com.android.application'
android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
}
apply android application编译所需的插件,然后配置android extension,最后配置依赖
Android插件配置 - Android Extension
可以查看之前博客介绍
Android Extension介绍
构建变体-Build Variant
build variant构建变体,感觉叫构建主体更准确,对应描述Android的编译目标,在module build.gradle中的Android Extension所配置的数据,最终就是为了定义并生成build variant
抛开Android不说,任何工程编译,其目标的确定都依赖如下两项
- build type - 编译类型
- product type - 目标产品
Android也一样,不同的是,Android在product定义上, 基于多维度组合配置生成,这样就使产品的定义更加灵活,也更加的强大
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "com.harish.test.sample"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
flavorDimensions "t1", "t2"
}
signingConfigs {
release {
storeFile file('keys/test.jks')
storePassword 'mainkey123'
keyAlias 'mainkey'
keyPassword 'mainkey123'
v2SigningEnabled true
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
debuggable true
jniDebuggable true
}
}
productFlavors{
t1{
dimension "t1"
}
t2{
dimension "t1"
}
t3{
dimension "t2"
}
}
}
上面这个例子中定义了两个dimension,t1和t2
flavorDimensions "t1", "t2"
维度的定义顺序代表依赖顺序,所以顺序不能随便定义,Android会根据定义产品的所属维度,两两组合+build type生成最终的build variant列表,比如上面最终生成的build variant为
- T1T3Debug
- T2T3Debug
- T1T3Release
- T2T3Release
维度只是在逻辑层面用于组合生成build variant,build variant的名字还是基于flavor name组合而成
Module库依赖 - Module Library dependencies
每个module工程的库依赖都是通过dependecies类配置的
dependencies {
api fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
}
当上面的DSL语句被执行后,module所依赖的库信息就被保存到module相关的project对象中,下面介绍project是如何保存依赖库信息的
先看project中dependencies的注释
/**
* <p>Configures the dependencies for this project.
*
* <p>This method executes the given closure against the {@link DependencyHandler} for this project. The {@link
* DependencyHandler} is passed to the closure as the closure's delegate.
*
* <h3>Examples:</h3>
* See docs for {@link DependencyHandler}
*
* @param configureClosure the closure to use to configure the dependencies.
*/
void dependencies(Closure configureClosure);
注释写的很清楚,dependencies的闭包执行的delegate是DependencyHandler,也就是
DefaultDependencyHandler,DefaultDependencyHandler的源码这里就不全部贴出了,这个类内部是没有实现api,implementation等函数的,但是在其构造函数中存在这样一行代码:
dynamicMethods = new DynamicAddDependencyMethods(configurationContainer, new DirectDependencyAdder());
看名字就知道,是DefaultDependencyHandler内部没有实现的函数调用,会动态转到DynamicAddDependencyMethods里处理,直接看其内部的处理函数
@Override
public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) {
if (arguments.length == 0) {
return DynamicInvokeResult.notFound();
}
Configuration configuration = configurationContainer.findByName(name);
if (configuration == null) {
return DynamicInvokeResult.notFound();
}
List<?> normalizedArgs = CollectionUtils.flattenCollections(arguments);
if (normalizedArgs.size() == 2 && normalizedArgs.get(1) instanceof Closure) {
return DynamicInvokeResult.found(dependencyAdder.add(configuration, normalizedArgs.get(0), (Closure) normalizedArgs.get(1)));
} else if (normalizedArgs.size() == 1) {
return DynamicInvokeResult.found(dependencyAdder.add(configuration, normalizedArgs.get(0), null));
} else {
for (Object arg : normalizedArgs) {
dependencyAdder.add(configuration, arg, null);
}
return DynamicInvokeResult.found();
}
}
先从configurationContainer中,根据依赖函数名获取configuration,然后调用
dependencyAdder.add添加到configuration中,看最终的添加代码
private Dependency doAdd(Configuration configuration, Object dependencyNotation, Closure configureClosure) {
if (dependencyNotation instanceof Configuration) {
Configuration other = (Configuration) dependencyNotation;
if (!configurationContainer.contains(other)) {
throw new UnsupportedOperationException("Currently you can only declare dependencies on configurations from the same project.");
}
configuration.extendsFrom(other);
return null;
}
Dependency dependency = create(dependencyNotation, configureClosure);
configuration.getDependencies().add(dependency);
return dependency;
}
通过dependencyNotation(根据依赖参数转化而成)创建Dependency,最后添加configuration中
现在我们明白了,gradle是怎么将依赖数据保存到project中的,总结如下
- project会创建configurationContainer
- 然后会预先创建好预定义的各种dependency所需的configuration,比如api,implementation,并保存到configurationContainer中
- 在build.gradle中配置project.dependencies DSL,将各种依赖根据函数名,保存到对应的configuration中
最终在编译时,从project的configurationContainer中根据依赖名,获取依赖配置数据
Tasks
Gradle工程的执行单位是task,我们可以在工程内部根据需要建立许多的task,然后为task建立依赖,最终在编译目标时,只需要执行末端task,然后末端task根据依赖会触发整个编译流程
所以,不管project的dependencies配置,还是Android gradle插件对应的Android extension配置到最终build variant的生成,最终的目的都是
- 生成编译所需的各种task
- 提供task执行所需的配置数据
Android gradle插件最终会生成一组assemble${BuildVariantName} task,我们可以输入
./gradlew build
编译全部build variant,也可以指定某一个
./gradlew assembleT1T3Debug
创建自定义task
在build.gradle或者插件中,我们可以通过project的成员函数创建task对象,既然是创建对象,那就必须要有关联的类,gradle创建task关联类分两种
- 创建task未指定,则使用默认的DefaultTask构建对象
- 创建task时通过type指定了目标task class,则使用目标task class构建对象
下面是默认task的创建
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
//配置依赖
taskX.dependsOn taskY
下面是指定type的task创建
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
task myCopy(type: Copy) {
}
这里的Jar和Copy就是在Gradle编译环境里已经实现的Task类,通过gradle源码,我们可以看到其实现,这里只贴出继承关系
/**
* Assembles a JAR archive.
*/
public class Jar extends Zip {
}
/**
* Assembles a ZIP archive.
*
* The default is to compress the contents of the zip.
*/
public class Zip extends AbstractArchiveTask {
}
public class Copy extends AbstractCopyTask {
}
当然,我们也可以使用自己创建的Task类来构建task对象
class GreetingTask extends DefaultTask {
@TaskAction
def greet() {
println 'hello from GreetingTask'
}
}
// Create a task using the task type
task hello(type: GreetingTask)
跟Extension一样,task也是支持namespace配置的
//通过namespace method
project.GreetingTask{
}
Artifact
我们可以在build.gradle配置要发布的artifacts, 这个配置主要对Library来说的,先来看一个自定义artifact的配置
configurations {
//declaring new configuration that will be used to associate with artifacts
schema
}
task schemaJar(type: Jar) {
//some imaginary task that creates a jar artifact with the schema
}
//associating the task that produces the artifact with the configuration
artifacts {
//configuration name and the task:
schema schemaJar
}
从上面的配置可以知道
- artifact的数据配置跟dependency一样,也是通过configuration保存的
- 通过artifact对应configuration保存的相关task,来完成发布产品的打包
configurations和task就不说了,直接看artifacts对应的DefaultArtifactHandler的实现,同样的,它在构造也实现了动态函数处理
dynamicMethods = new DynamicMethods();
接着看DynamicMethods的内部实现
@Override
public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) {
if (arguments.length == 0) {
return DynamicInvokeResult.notFound();
}
Configuration configuration = configurationContainer.findByName(name);
if (configuration == null) {
return DynamicInvokeResult.notFound();
}
List<Object> normalizedArgs = GUtil.flatten(Arrays.asList(arguments), false);
if (normalizedArgs.size() == 2 && normalizedArgs.get(1) instanceof Closure) {
return DynamicInvokeResult.found(pushArtifact(configuration, normalizedArgs.get(0), (Closure) normalizedArgs.get(1)));
} else {
for (Object notation : normalizedArgs) {
pushArtifact(configuration, notation, Actions.doNothing());
}
return DynamicInvokeResult.found();
}
}
根据函数名从configurationContainer拿出对应的configuration,接着回到DefaultArtifactHandler的pushArtifact函数将artifact添加到configuration中
private PublishArtifact pushArtifact(Configuration configuration, Object notation, Action<? super ConfigurablePublishArtifact> configureAction) {
ConfigurablePublishArtifact publishArtifact = publishArtifactFactory.parseNotation(notation);
configuration.getArtifacts().add(publishArtifact);
configureAction.execute(publishArtifact);
return publishArtifact;
}
MavenPublication
Android库maven发布配置
apply plugin: 'maven'
apply plugin: 'maven-publish'
Properties config = new Properties()
config.load(project.file("nexus.properties").newDataInputStream())
def nexus_versionName = config.getProperty('nexus_versionName')
def nexus_artifactId = config.getProperty('nexus_artifactId')
def nexus_groupId = config.getProperty('nexus_groupId')
def nexus_description = config.getProperty('nexus_description')
def nexus_fileName = config.getProperty('nexus_fileName')
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
pom.groupId = GROUP_ID
pom.artifactId = nexus_artifactId
pom.packaging = 'aar'
pom.name = nexus_fileName
pom.version = nexus_versionName
repository(url:uri(LOCAL_REPO_URL))
}
}
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
}
}
简单说下
- uploadArchives是task,它是maven-publish插件添加的
- archives是gradle预置的configuration,可以往其添加新的artifact task
ExtraPropertiesExtension
ExtraPropertiesExtension也就是我们在工程配置中经常中的ext属性,先看官方文档怎么说的:
Extra properties extensions allow new properties to be added to existing domain objects. They act like maps, allowing the storage of arbitrary key/value pairs. All ExtensionAware Gradle domain objects intrinsically have an extension named “ext” of this type.
- ext属性允许给已有的领域对象动态的添加属性
- 任何ExtensionAware的领域对象,默认就会包含ext
看看哪些Gradle类是ExtensionAware的
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {}
public interface Task extends Comparable<Task>, ExtensionAware{}
public interface Settings extends PluginAware, ExtensionAware {}
其他不说,Project,Task,Settings对应的对象默认肯定是包含ext的
ext的配置和读取
project.ext {
myprop = "a"
}
project.myprop = "a"
project.ext.myprop == "a"
project.ext["otherProp"]
project["otherProp"]
project.properties["myprop"]
参考文献
https://segmentfault.com/a/1190000019211742
https://docs.gradle.org
http://google.github.io/android-gradle-dsl