由浅入深理解android gradle

547 阅读6分钟

gradle项目的结构

首先将android项目中的gradle文件系统列举如下:

  • settings.gradle
  • project路径下的build.gradle
  • subproject(也可以理解为module)路径下的build.gradle

接下来我们依次分析他们的组成结构,并且总结各个gradle文件的作用

gradle文件使用groovy编写。但是大量使用DSL,一定程度上可以使代码的功能容易看懂,但是加大了读懂具体实现的难度。

settings.gradle

一般这个文件下只包含一句话,比如

include ':app', ':module_a', ':module_b', ':base'

这里调用了一个include方法,传入了一个字符串数组,表示在这个gradle文件系统下,包含以下这么多个subproject。

project路径下的build.gradle

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

代码块整体是一个构建脚本,里面又是由两部分构成:

  1. 配置了代码仓库(与下面的仓库不同点在于这里是提供最外层project构建用的仓库)。
  2. 配置了依赖,具体区别同上。

所以整体上可以这么理解,如果有project(最外层项目的构建需求),我们就从这里配置的仓库中取出这里配置的依赖中的代码库。


allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

这里的配置是给所有的subproject用的,比如这里给所有的subproject配置了google和jcenter仓库。


task clean(type: Delete) {
    delete rootProject.buildDir
}

这里在build.gradle中创建了一个clean任务,用来删除最外层project的构建目录。

subproject路径下的build.gradle

apply plugin: 'com.android.application'
apply plugin: 'com.android.library'

如果此时构建的是一个android项目,那么subproject的build.gradle则需要引用上面两个插件(都是android官方提供给我们来构建android项目的)中的一个,前者用来配置一个application类型的module,后者用来配置一个library类型的module。

这里特别需要特别注意一下apply plugin和apply from两个概念。

apply from(应用脚本插件)

可以直接调用插件中的脚本,就好像脚本插件就是当前gradle文件的一部分一样。

apply plugin(应用二进制插件)

调用二进制插件中的apply方法,把当前project对象当作参数传入


android {
    compileSdkVersion 28
    buildToolsVersion "29.0.0"
    defaultConfig {
        applicationId "com.example.neteasemusic"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility = '1.8'
        targetCompatibility = '1.8'
    }
}

接下来是这一段,方法名android,很明显是用来配置各种android属性的,比如编译sdk的版本,构建工具的版本,各种默认配置,构建类型,编译的jdk版本,等等。


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    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'

    implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
}

这里就更好理解了,当前这个module依赖的第三方库,就在这里配置。

如何理解gradle文件

我们知道setting.gradle,可以对应到一个Settings.java文件。build.gradle,可以对应到一个Project.java文件。那他们之间是什么关系呢?

我现在的理解是,build.gradle文件,用groovy语言编写,在内部会持有一个Project的对象,在build.gradle中写的代码,都是用持有的这个Project类的对象在调用。

如何理解gradle版本和gradle插件版本

android项目使用gradle作为构建工具,gradle版本就是当前项目使用的gradle sdk的版本

android官方提供了几个用于构建module的插件(上面提到过的com.android.application和com.android.library),gradle插件版本就是指这几个插件的版本。

我在这里用一个可能不恰当的比方。gradle版本可以类比成jdk的版本,gradle插件版本则是android sdk的版本。

自定义gradle插件

我们一起分析gradle官网给出的例子:

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello') {
            doLast {
                println 'Hello from the GreetingPlugin'
            }
        }
    }
}

// Apply the plugin
apply plugin: GreetingPlugin

上面是第一种方法,首先创建了一个GreetingPlugin实现了Plugin接口,重写了apply方法,在project中创建了一个名为hello的task,在task执行完后打印了一句话"Hello from the GreetingPlugin".

然后在当前gradle文件中应用了这个plugin。

所以当项目构建完成后,会多出来一个名为hello的task,运行这个task,会在控制台打出上面那句话


class GreetingPluginExtension {
    String message = 'Hello from GreetingPlugin'
}

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        // Add the 'greeting' extension object
        def extension = project.extensions.create('greeting', GreetingPluginExtension)
        // Add a task that uses configuration from the extension object
        project.task('hello') {
            doLast {
                println extension.message
            }
        }
    }
}

apply plugin: GreetingPlugin

// Configure the extension
greeting.message = 'Hi from Gradle'

上面是第二种方法,首先创建了一个GreetingPluginExtension类,可以理解为一个实体类,这个类里面有一个字段message,我们待会就会用到。

接下来我们创建了一个GreetingPlugin实现了Plugin接口,重写了apply方法,拿到project对象中的extensions,创建了一个名为greeting,类型为GreetingPluginExtension的对象,然后project创建了一个名为hello的task,在任务完成后打印出了extension中的message。

然后在当前gradle文件中应用了这个插件,所以project中创建了一个extension,然后创建了一个task

最后给extension中的字段赋值。

打包一个plugin

前面我们讲了如何写gradle插件,那接下来自然就是如何打包使用了,一般来说,我们自己写的gradle plugin的source code可以放在以下几个地方:

  • 构建脚本
    你可以把插件的代码直接写在构建脚本中。好处是插件是自动编译和包含在构建脚本的类路径中的,也就意味着你不需要额外做任何事。然后,在构建脚本意外插件是不可见的,所以你不能在其他地方使用你的插件。

  • buildSrc project中
    你可以把插件的源码放在rootProjectDir/buildSrc/src/main/groovy目录下。Gradle会自动进行编译,测试,并且使这个插件在构建脚本的类路径上可用。

  • 独立的project中
    你可以为你的plugin创建一个单独的项目。这个项目产生和发布一个jar包,供其他人使用。一般来说,这个jar包会包含一些插件,

gradle构建android项目的过程

我们直接观察项目构建时IDE的「build」窗口的输出。可以直接看出,一次完整的构建包含如下四个部分:

  1. load build(加载构建)
  2. configure build(配置构建)
  3. calculate task graph(计算task的图)
  4. run tasks(运行用于构建项目的tasks)

我们依次分析这四个部分:


第一部分,load build,截图中包含了两个部分,第一部分可以忽略(是我用来存放自定义plugin的代码相关的)。第二部分解析了根目录下面的settings.gradle文件。


第二,三部分比较清晰,我们可以放在一起讲,第二部分主要是加载了所有subprojects,第三部分则是计算了整个gradle项目所有task的有向无环图。


第四部分,在上一部分所有task的有向无环图产生之后,就可以自由的运行我们想运行的task了。所以这里运行了所有构建需要的task。