gradle打包流程(二)--- 进一步理解gradle

一、gradle是什么

1、gradle是一个自动化构建工具。

gradle是通过组织一系列task来最终完成自动化构建的,所以task是gradle里最重要的概念。以生成一个可用的apk为例,整个过程要经过资源的处理,javac编译,dex打包,apk打包,签名等等步骤,每个步骤就对应到gradle里的一个task。

2、gradle使用groovy或者kotlin编写。

groovy是DSL。那么什么是DSL?DSL也就是Domain Specific Language的简称,也就是领域特定语言,是为了解决某一类任务专门设计的计算机语言。DSL使用简单,定义比较简洁。

3、无论是基于groovy还是kotlin编写,均是基于jvm的语言。

因此本质上都是面对对象的,面向对象的特点是一切皆为对象,所以,在gradle里,.gradle脚本的本质就是类的定义,一些配置项的本质都是方法调用,参数是后面的{}闭包。比如build.gradle对应Project类,buildScript对应Project.buildScript方法。

二、gradle项目分析

首先看一下gradle的项目层次,下面是一张示例图:

 可结合gradle打包流程(一)进行查看。

1、settings.gradle

settings.gradle是负责配置项目的脚本。

对应Settings类,gradle构建过程中,会根据settings.gradle生成Settings的对象,其中有几个主要的方法:

  • include(projectPaths)
  • includeFlat(projectNames)
  • project(projectDir)

一般在项目里见到的引用子项目模块的方法,就是使用include,这样引用,子模块位于根目录下一级

include ':app'

如果是多个项目,则类似于

include ':app', ':library'

如果想指定子模块的位置,可以使用project方法去获取Project对象,设置其projectDir参数

include ':app'
project(':app').projectDir = new File('./app')

2、rootproject/build.gradle

build.gradle负责整体项目的一些配置,对应的是Project类。

gradle构建的时候,会根据build.gradle生成Project对象,所以在build.gralde里写的DSL,其实都是Project接口的一些方法,Project其实是一个接口,真正的实现类是DefaultProject。Project其中有几个主要的方法:

  • buildscript //配置脚本的classpath
  • allprojects //配置项目及其子项目
  • respositories //配置仓库地址,后面的依赖都会去这里配置的地址查找
  • dependencies //配置项目的依赖

接上面的介绍gradle项目层级的示例图对应的项目UI举例,其rootproject/build.gradle的内容如下:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from:"config.gradle" //引入config.gradle
buildscript {  //配置项目的classpath
    repositories {  //项目的仓库地址,会按顺序依次查找
        google()
        jcenter()
    }
    dependencies {  //项目的依赖
        classpath "com.android.tools.build:gradle:4.1.2"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {  //子项目的配置
    repositories {
        google()
        jcenter()
    }
}

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

3、module/build.gradle

build.gradle是模块的配置,对应的也是Project类。

作为模块级build.gradle与顶层build.gradle相比,是差不多的,但是我们也可以看到一个明显的区别,就是在模块级build.gradle中,引用了一个插件 apply plugin: 'com.android.application'或者apply plugin: 'com.android.library'或者apply plugin: 'java-library' 等等。比如说apply plugin: 'com.android.application',后面的android dsl就是application插件的extension。

其中有几个主要方法:

  • compileSdkVersion //编译sdk版本,也就是指定gradle编译应用时用的Android API级别
  • defaultConfig //默认的配置
  • buildTypes //编译类型,一般有debug和release
  • productFlavor //产品变种

我们以项目UI中的app module的build.gradle来看,

plugins {
    id 'com.android.application'
}  //引入android gradle插件

android {  //配置android gradle plugin需要的内容
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {  //默认配置
        applicationId "com.zdj.uidemo"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {  //编译类型
        release {  //release版本
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {  //指定java版本
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {  //模块需要的依赖

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.navigation:navigation-fragment:2.2.2'
    implementation 'androidx.navigation:navigation-ui:2.2.2'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    if (rootProject.ext.isModule.toBoolean()) {
        implementation project(':phone')
    } else {
        implementation project(':zdjuilibrary')
    }
}

4、依赖 

新配置

弃用配置

行为

作用

implementation

compile

依赖项在编译时对模块可用,并且仅在运行时对模块的消费者可用。对于大型多项目构建,使用implementation而不是api/compile可以显著缩短构建时间,因为它可以减少构建系统需要重新编译的项目量。大多数应用和测试模块都应使用此配置。

api只会暴露给直接依赖的模块,使用此配置,在模块修改以后,只会重新编译直接依赖的模块,间接依赖的模块不需要改动。

api

compile

依赖项在编译时对模块可用,并且在编译时和运行时还对模块的消费者可用。此配置的行为类似于compile(现在已弃用),一般情况下,应当仅在库模块中使用它。应用模块应使用implementation,除非想要将其API公开给单独的测试模块。

api会暴露给间接依赖的模块,使用此配置,在模块修改以后,模块的直接依赖和间接依赖的模块都需要重新编译。

compileOnly

provided

依赖项仅在编译时对模块可用,并且在编译或运行时对其消费者不可用。此配置的行为类似于provided(现在已弃用)。

只在编译期间依赖模块,打包以后运行时不会依赖,可以用来解决一些库冲突的问题。

runtimeOnly

apk

依赖项仅在运行时对模块及其消费者可用。此配置的行为类似于apk(现在已弃用)。

只在运行时依赖模块,编译时不依赖。

5、flavor

几个概念:flavor,dimension,variant。

在android gradle plugin 3.x之后,每个flavor必须对应一个dimension,可以理解为flavor的分组,然后不同dimension里的flavor两两组和形成一个variant。

举例如下:

flavorDimensions "size", "color"
productFlavors {
    big {
        dimension "size"
    }
    small {
        dimension "size"
    }
    red {
        dimension "color"
    }
    green {
        dimension "color"
    }
}

那么生成的variant对应的就是bigRed,bigGreen,smallRed,smallGreen。

每个variant可以对应的使用variantImplementation来引入特定的依赖,比如:

bigRedImplementation,只有在编译bigRed variant的时候才会引入。

三、gradle wrapper 

gradlew/gradlew.bat这个文件用来下载特定版本的gradle然后执行的,就不需要开发者在本地再安装gradle了。这样做的好处是:开发者在本地安装gradle,会碰到不同项目使用不同版本的gradle无法处理的问题,那么使用wrapper就很好地解决了这个问题。可以在不同项目里使用不同的gradle版本。

gradle/wrapper/gradle-wrapper.properties 是一些gradlewrapper的配置,其中用的比较多的就是distributionUrl,可以执行gradle的下载地址和版本。

gradle/wrapper/gradle-wrapper.jar 是gradlewrapper运行需要的依赖包。

四、gradle生命周期及回调

gradle构建分为三个阶段:

初始化阶段、配置阶段、执行阶段。

初始化阶段:这个阶段主要做的事情是有哪些项目需要被构建,然后为对应的项目创建Project对象。

配置阶段:这个阶段主要做的事情是对上一步创建的项目进行配置,这时候会执行build.gralde脚本,并且会生成要执行的task。

执行阶段:这个阶段主要做的事情就是执行task,进行主要的构建工作。

gradle在构建过程中,会提供一些回调接口,方便在不同的阶段做一些事情,主要的接口有下面几个:

gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println('构建开始')
        // 这个回调一般不会调用,因为我们注册的时机太晚,注册的时候构建已经开始了,是 gradle 内部使用的
    }

    @Override
    void settingsEvaluated(Settings settings) {
        println('settings 文件解析完成')
    }

    @Override
    void projectsLoaded(Gradle gradle) {
        println('项目加载完成')
        gradle.rootProject.subprojects.each { pro ->
            pro.beforeEvaluate {
                println("${pro.name} 项目配置之前调用")
            }
            pro.afterEvaluate{
                println("${pro.name} 项目配置之后调用")
            }
        }
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println('项目解析完成')
    }

    @Override
    void buildFinished(BuildResult result) {
        println('构建完成')
    }
})

gradle.taskGraph.whenReady {
    println("task 图构建完成")
}
gradle.taskGraph.beforeTask {
    println("每个 task 执行前会调这个接口")
}
gradle.taskGraph.afterTask {
    println("每个 task 执行完成会调这个接口")
}

五、自定义task 

默认创建task,继承自DefaultTask。

task myTask {
    println 'myTask in configuration'
    doLast {
        println 'myTask in run'
    }
}

class MyTask extends DefaultTask {
    @Input Boolean myInputs
    @Output
    @TaskAction
    void start() {
    }
}

tasks.create("mytask").doLast {
}

Task的一些重要方法分类如下:

  • Task行为:Task.doFirst, Task.doLast
  • Task依赖顺序:Task.dependsOn, Task.mustRunAfter, Task.shouldRunAfter, Task.finalizedBy
  • Task的分组描述:Task.group, Task.decription
  • Task是否可用:Task.enabled
  • Task输入输出:Task.inputs, Task.outputs, gradle会比较task的inputs和outputs来决定task是否是最新的,如果inputs和outputs没有变化,则认为task是最新的,task就会跳出不执行。
  • Task是否执行:可以通过指定Task.upToDateWhen = false来强制task执行Task.upToDateWhen

比如说我们要指定Task之间的依赖顺序,可以这样写:

task task1 {
    doLast {
        println('task2')
    }
}
task task2 {
    doLast {
        println('task2')
    }
}
task1.finalizedBy(task2)
task1.dependsOn(task2)
task1.mustRunAfter(task2)
task1.shouldRunAfter(task2)
task1.finalizedBy(task2)

六、Android Transform 

android gradle plugin 提供了transform api用来在class文件转成dex过程中对class进行处理,可以理解为一种特殊的Task,因为transform最终也会转化为Task去执行。

要实现transform需要继承com.android.build.api.tranform.Transform并实现其方法,实现了Transform以后,要想应用,就调用project.android.registerTransform()

public class MyTransform extends Transform {
    @Override
    public String getName() {
        // 返回transform的名称,最终的名称会是transformClassesWithMyTransformForDebug这种形式
        return "MyTransform";
    }
    
    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        /**
        返回需要处理的数据类型,有下面几种类型可选
        public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
        public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES); 
        public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
        public static final Set<ContentType> CONTENT_NATIVE_LIBS = ImmutableSet.of(NATIVE_LIBS);
        public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX);
        public static final Set<ContentType> DATA_BINDING_ARTIFACT = ImmutableSet.of(ExtendedContentType.DATA_BINDING);
        */
        return TransformManager.CONTENT_CLASS;
    }
    
    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
        /**
        返回需要处理内容的范围,有下面几种类型
        PRIOJECT(1),只处理项目的内容
        SUB_PROJECTS(4),只处理子项目
        EXTERNAL_LIBRARIES(16),只处理外部库
        TESTED_CODE(32),只处理当前variant对应的测试代码
        PROVIDED_ONLY(64),处理依赖
        @Deprecated
        PROJECT_LOCAL_DEPS(2)
        @Deprecated
        SUB_PROJECTS_LOCAL_DEPS(8);
        */
        return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT);
    }
    
    @Override
    public boolean isIncremental() {
        //是否增量,如果返回true,TransformInput会包括一份修改的文件列表,返回false,会进行全量编译,删除上一次的输出内容
        return false;
    }
    
    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        //在这里处理class
        super.transform(transformInvocation)
        //在transform里,如果没有任何修改,也要把input的内容输出到output,否则会报错
        for (TransformInput input : transformInvocation.inputs) {
            input.directoryInputs.each { dir ->
                //获取对应的输出目录
                File output = transformInvocation.outputProvider.getContentLocation(dir.name, dir.contentTypes, dir.scopes, Format.DIRECTORY)
                dir.changedFiles //增量模式下修改的文件
                dir.file //获取输入的目录
                FileUtils.copyDirectory(dir.file, output) //input内容输出到output
            }
            input.jarInputs.each { jar ->
                //获取对应的输出jar
                File output = transformInvocation.outputProvider.getContentLocation(jar.name, jar.contentTypes, jar.scopes, Format.JAR)
                jar.file //获取输入的jar文件
                FileUtils.copyFile(jar.file, output) //input内容输出到output
                
            }
        }
    }
}

//注册transform
android.registerTransform(new MyTransform())

在transform中的处理,一般会涉及到class文件到修改,操纵字节码的工具一般是javasist和asm居多。

七、手写plugin 

gradle的插件可以看作是一系列task的集合。

在android工程的app module/build.gradle脚本里,第一行就是apply plugin: 'com.android.application' ,这个就是引入android gradle插件,插件里有android打包相关的task。

那么我们现在看看,如何实现一个自己的plugin呢?

1、初始化工程

(1)、在android studio中创建一个java module

(2)、在src/main目录下创建groovy目录,然后创建自己的包名和插件类

(3)、在src/main目录下创建resources/META-INFO/gradle-plugins目录,创建plugins.properties文件,文件内容为:

implementation-class=com.zdj.plugin.MyPlugin //我们自己的插件类

(4)、修改build.gradle文件

apply plugin: 'groovy'
apply plugin: 'java'

buildscript {
    repositories {
        mavenLocal()
        google()
        jcenter()
    }
}

repositories {
    mavenLocal()
    google()
    jcenter()
}

dependencies {
    compile gradleApi()
    compile localGroovy()
    compile 'com.android.tools.build:gradle:3.0.1'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

2、创建Plugin 

在刚才创建的插件类里,我们写插件的代码。插件类继承Plugin,并实现apply接口,apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口了。

package com.zdj.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

/**
 * 在这个类中写插件的代码。
 * 首先继承Plugin,然后实现apply接口。
 * apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口。
 */
class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project target) {
        println("apply my plugin")
    }
}

3、创建插件的task 

我们再定义一个类MyTask,继承自DefaultTask。

package com.zdj.plugin

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

/**
 * 定义一个类MyTask,继承自DefaultTask。
 */
class MyTask extends DefaultTask {
    @TaskAction
    void action() {
        println("my task run")
    }
}

然后在plugin中注册这个task。

package com.zdj.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

/**
 * 在这个类中写插件的代码。
 * 首先继承Plugin,然后实现apply接口。
 * apply就是在build.gradle里apply plugin 'xxx'的时候要调用的接口。
 */
class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project target) {
        println("apply my plugin")
        //注册task
        target.tasks.create("mytask", MyTask.class)
    }
}

4、本地安装插件

我们已经开发好了一个简单的插件,那么如何使用呢?

我们首先需要在build.gradle中引入maven插件,并且配置install相关的属性。

apply plugin: 'maven'

install {
    repositories.mavenInstaller {
        pom.version = '0.0.1'  //配置插件版本号
        pom.artifactId = 'myplugin'  //配置插件标识
        pom.groupId = 'com.zdj.plugin'  //配置插件组织
    }
}

之后执行./gradlew install便会把插件安装在本地maven仓库。

本地安装插件执行./gradlew install命令是可能会报错could not find tools.jar问题。我遇到了这个问题。

我的解决办法是:先查看JDK的目录路径(通过命令 /usr/libexec/java_home -V),然后手动复制tools.jar(通过命令sudo cp)。

5、使用我们的插件 

在使用的地方引入我们插件的classpath

classpath 'com.zdj.plugin:myplugin:0.0.1'

最后,附上两个重要的查询地址:

1、

Gradle DSL Version 7.3.3

(gradle dsl查询地址)

2、

https://developer.android.com/reference/tools/gradle-api

(android gradle plugin dsl查询地址) 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值