Gradle - Android工程结构和编译相关介绍

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脚本,则脚本的加载顺序是

  1. 编译时指定的gradle脚本
  2. GRADLE_HOME/init.gradle

还有,gradle在初始化时,默认还会按如下顺序读取gradle.properties配置工程的全局属性

  1. GRADLE_HOME/gradle.properties
  2. 工程根目录的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,用于配置工程全局的数据,包括:

  1. gradle构建环境所需的依赖class path,对应buildscript{}
  2. 工程编译所需的全局配置,对应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编译配置,主要包括:

  1. apply工程所需的各种gradle插件
  2. 配置各个插件创建的extension(如果有的话)
  3. 配置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不说,任何工程编译,其目标的确定都依赖如下两项

  1. build type - 编译类型
  2. 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中的,总结如下

  1. project会创建configurationContainer
  2. 然后会预先创建好预定义的各种dependency所需的configuration,比如api,implementation,并保存到configurationContainer中
  3. 在build.gradle中配置project.dependencies DSL,将各种依赖根据函数名,保存到对应的configuration中

最终在编译时,从project的configurationContainer中根据依赖名,获取依赖配置数据

Tasks

Gradle工程的执行单位是task,我们可以在工程内部根据需要建立许多的task,然后为task建立依赖,最终在编译目标时,只需要执行末端task,然后末端task根据依赖会触发整个编译流程

所以,不管project的dependencies配置,还是Android gradle插件对应的Android extension配置到最终build variant的生成,最终的目的都是

  1. 生成编译所需的各种task
  2. 提供task执行所需的配置数据

Android gradle插件最终会生成一组assemble${BuildVariantName} task,我们可以输入

./gradlew build

编译全部build variant,也可以指定某一个

./gradlew assembleT1T3Debug

创建自定义task

在build.gradle或者插件中,我们可以通过project的成员函数创建task对象,既然是创建对象,那就必须要有关联的类,gradle创建task关联类分两种

  1. 创建task未指定,则使用默认的DefaultTask构建对象
  2. 创建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
}

从上面的配置可以知道

  1. artifact的数据配置跟dependency一样,也是通过configuration保存的
  2. 通过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

发布了46 篇原创文章 · 获赞 25 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/zhejiang9/article/details/100525651