Android Gradle Study

深入理解Android Gradle
Alternatives To Android Gradle Study
Project NameStarsDownloadsRepos Using ThisPackages Using ThisMost Recent CommitTotal ReleasesLatest ReleaseOpen IssuesLicenseLanguage
Kotlin44,2151,5233,60010 hours ago77September 07, 2022151Kotlin
The Kotlin Programming Language.
Androiddevtools7,528
8 days ago4
收集整理Android开发所需的Android SDK、开发中用到的工具、Android开发教程、Android设计规范,免费的设计素材等。
Gradle Retrolambda5,3886834 years ago40April 21, 201974apache-2.0Java
A gradle plugin for getting java lambda support in java 6, 7 and android
Packer Ng Plugin4,7963219 months ago12March 03, 201721apache-2.0Java
下一代Android打包工具(对Gradle 7.x的支持,欢迎提PR)
Gradle Versions Plugin3,5531184 days ago20May 31, 201773apache-2.0Kotlin
Gradle plugin to discover dependency updates
Gradle_plugin_android_aspectjx3,499
2 years ago145apache-2.0Groovy
A Android gradle plugin that effects AspectJ on Android project and can hook methods in Kotlin, aar and jar file.
Shadow3,212
4 days ago216apache-2.0Groovy
Gradle plugin to create fat/uber JARs, apply file transforms, and relocate packages for applications and libraries. Gradle version of Maven's Shade plugin.
Aboutlibraries3,1691a month ago87August 19, 20228apache-2.0Kotlin
AboutLibraries automatically collects all dependencies and licenses of any gradle project (Kotlin MultiPlatform), and provides easy to integrate UI components for Android and Compose-jb environments
Dexcount Gradle Plugin3,008
2 days ago45January 25, 20222apache-2.0Java
A Gradle plugin to report the number of method references in your APK on every build.
Dokka2,8644a day ago13June 19, 2022356apache-2.0Kotlin
API documentation engine for Kotlin
Alternatives To Android Gradle Study
Select To Compare


Alternative Project Comparisons
Readme

一文读懂Android Gradle

为什么写这个?

讲Gradle的文章和书很多,讲Groovy的文章和书也很多,但是在Android中如何使用Gradle和Groovy,感觉没有一篇文章和书能够讲透,总觉得使用起来模模糊糊,云里雾里。所以,想把平时研究和应用Gradle的一些要点和心得记录下来,既是方便自己,也是方便大家。

  • [x] 会持续更新学习和研究Gradle的一些要点和心得,力求让你从根本上搞懂Android Gradle,不求大而全,但求精。也许你在别处找不到的答案,在这里可以找到呢。
  • [x] 仓库中的工程是为了方便我经常调试和研究Gradle所搭建,可能有点乱,仅做参考。

Gradle是什么

“Gradle is an open-source build automation system that builds upon the concepts of Apache Ant and Apache Maven and introduces a Groovy-based domain-specific language (DSL) instead of the XML form used by Apache Maven for declaring the project configuration.[1] Gradle uses a directed acyclic graph ("DAG") to determine the order in which tasks can be run.”——维基百科对Gradle的定义。

翻译过来就是:“Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。”

还是很难懂。难怪有些人觉得gradle文件看起来很痛苦,看不懂。我的理解是:gradle既是脚本,也是代码。可以像脚本那些执行,而且每一行脚本,都可以理解成执行了相应的对象中的一个方法。但是由于闭包的存在使得有些代码的执行顺序跟定义的顺序不一致。这样一来,Gradle写起来和读起来都像配置文件,实际上是一系列代码,要以代码的角度来阅读和编写Gradle。这样一来,Gradle就要好理解得多。

AS中的Gradle Build Script

  1. 工程根目录下的setting.gradle和build.gradle
  2. 每个Module目录下都有一个build.gradle

执行顺序

Gradle执行的时候遵循如下顺序:

  1. 首先解析settings.gradle来获取模块信息,这是初始化阶段
  2. 然后配置每个模块,配置的时候并不会执行task
  3. 配置完了以后,有一个重要的回调project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行task了
  4. 执行指定的task。

gradle对象

每个gradle脚本都可以访问gradle对象,比如在setting.gradle下执行:

println("gradle name: " + gradle.class.name)

会输出:

gradle name: org.gradle.invocation.DefaultGradle_Decorated

这些gradle对象都是接口Gradle的实现:

public interface Gradle extends PluginAware {
    ....
}

比如有个叫DefaultGradle的实现:

public class DefaultGradle extends AbstractPluginAware implements GradleInternal {
...
}

通过gradle对象可以获取Gradle的相关信息和添加一些钩子。

setting对象

每一个setting.gradle都对应一个setting对象

在setting.gradle中可以访问到setting对象:

println("setting.gradle: " + settings)
println("setting.gradle: " + this)

输出:

setting.gradle: settings 'android-gradle-study'
setting.gradle: settings 'android-gradle-study'

project对象

每一个build.gradle都对应一个project对象。

  • [x] 在project的build.gradle中,获取到的project对象是root project
  • [x] 在module的build.gradle中,获取到的project对象是module project

比如在root project的build.gradle中和在module的build.gradle中执行如下代码:

println("Root build.gradle: " + project)
println("Root build.gradle: " + this)

root project输出:

Root build.gradle: root project 'android-gradle-study'
Root build.gradle: root project 'android-gradle-study'

module project输出:

App build.gradle: project ':app'
App build.gradle: project ':app'

但是,root project和module project的类型是一样的,都是DefaultProject。

Root build.gradle

buildscript

buildscript用于配置插件的classpath,插件跟引用的aar不同,插件不会编译到apk中,插件只是用于构建。设置repositories告诉gradle classpath的仓库地址,dependencies用于配置具体的classpath。

allprojects

allprojects进行的配置会应用到当前的project以及其所有module,这里配置的repositories会在当前的project以及其所有module都生效。

Gradle Wrapper

Gradle Wrapper,就是对gradle的一层包装。在AS右侧的Gradle面板直接运行task等同于直接用gradle运行task。但是由于gradle有不同的版本,所以希望使用统一的gradle版本进行构建,避免由于gradle版本不统一带来的问题。

AS的工程下有两个脚本:gradlew和gradlew.bat。包括还有一个文件夹:gradle/wrapper/这个文件夹里面的gradle-wrapper.properties决定了我们使用gradlew的时候调用的gradle版本:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

gradlew下载的gradle一般放在如下的位置:

~/.gradle/wrapper/dists

Groovy 闭包

闭包就是一段代码块,以参数的形式传递给其他函数,其他函数可以执行这个闭包。这有点像java里面listener的作用。 示例:

task doSomething1 << {
    runEach({
        println(it)
    })
}

def runEach(closure) {
    for (int i in 1..10) {
        closure(i)
    }
}

由于Groovy里面方法调用可以省略括号,并且如果方法的最后一个参数是闭包,可以放到方法外面,因此我们经常可以方法调用闭包的写法类似这样:

runEach {
    println(it)
}

Task

Gradle里面创建task的方法有很多,我最喜欢的一种是任务名字+闭包配置的方式:

task demoTask {
    description 'demo task'
}

查看源码可以发现,task都是通过TaskContainer创建的:

public Task task(String task) {
    return this.taskContainer.create(task);
}

一个task由多个action组成,task真正要完成的任务都放在这些action里面。

在源码AbstractTask中,我们可以找到一个存放action的list:

public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
    private static final Logger BUILD_LOGGER = Logging.getLogger(Task.class);
    private static final ThreadLocal<TaskInfo> NEXT_INSTANCE = new ThreadLocal<TaskInfo>();

    private final ProjectInternal project;

    private final String name;

    private List<ContextAwareTaskAction> actions;

    ...
}

我们可以通过doFirst和doLast来添加action,doFirst把action添加到最前面,doLast添加action最后执行:

task demoTask {
    description 'demo task'
    doFirst {
        println("this action will run first!")
    }
    doLast {
        println("this action will run last!")
    }
}

查看Task的源码可以发现,这两个操作无非是把action添加到action list的队头和队尾:

@Override
public Task doFirst(final Closure action) {
    hasCustomActions = true;
    if (action == null) {
        throw new InvalidUserDataException("Action must not be null!");
    }
    taskMutator.mutate("Task.doFirst(Closure)", new Runnable() {
        public void run() {
            getTaskActions().add(0, convertClosureToAction(action, "doFirst {} action"));
        }
    });
    return this;
}

@Override
public Task doLast(final Closure action) {
    hasCustomActions = true;
    if (action == null) {
        throw new InvalidUserDataException("Action must not be null!");
    }
    taskMutator.mutate("Task.doLast(Closure)", new Runnable() {
        public void run() {
            getTaskActions().add(convertClosureToAction(action, "doLast {} action"));
        }
    });
    return this;
}

<<操作符

因为Task的doLast用的很多,所以使用了一种doLast的短标记形式,这就是<<操作符:

task doSomething1 << {
    //doLast的action
}

<<对应的源码是Task的leftShift方法:

@Override
public Task leftShift(final Closure action) {
    DeprecationLogger.nagUserWith("The Task.leftShift(Closure) method has been deprecated and is scheduled to be removed in Gradle 5.0. Please use Task.doLast(Action) instead.");

    hasCustomActions = true;
    if (action == null) {
        throw new InvalidUserDataException("Action must not be null!");
    }
    taskMutator.mutate("Task.leftShift(Closure)", new Runnable() {
        public void run() {
            getTaskActions().add(taskMutator.leftShift(convertClosureToAction(action, "doLast {} action")));
        }
    });
    return this;
}

这也进一步说明了doLast和<<操作符是一致的。

Plugin

Plugin用来干嘛?

研究一项技术之前如果不弄清楚这项技术用来干嘛,能带来什么好处,那就是为了研究技术而研究技术,没任何卵用。

Plugin说白了就是可以把你之前写在gradle文件中的那些代码,提取出来,放到一个插件中。这个插件可以放到一个仓库中,可以下载下来使用。说白了,就是提高你那段代码的复用性。

使用Plugin

apply plugin:[your-plugin]

当你调用这句话的时候,你写在你的自定义Plugin中的apply方法就会执行。就这么简单。

在使用plugin之前,需要在root project的build.gradle中指定classpath和相应的repo仓库地址:

buildscript {
    repositories {
        //定义repo仓库地址
    }
    dependencies {
        //定义classpath
    }
}

设置buildscript的目的是为了让gradle能知道去哪找到你的插件。

自定义Plugin

自定义Plugin很简单,直接参考代码里面的plugin module即可。

自定义的Plugin可以采用Groovy编写,也可以采用java编写。采用Groovy编写可以使用Groovy的一些特性,比如使用闭包。

你甚至可以像示例中的plugin module那样,同时使用groovy和java。并且可以在一个module,或者一个jar中包含多个plugin。

配置一个plugin module可以参考示例代码,其中有几个关键点:

  • [x] apply plugin
apply plugin: 'groovy'
apply plugin: 'java'
  • [x] 配置sourceSets
sourceSets {
    main {
        groovy {
            srcDir 'src/main/groovy'
        }

        java {
            srcDir 'src/main/java'
        }
    }
}
  • [x] 配置dependencies
dependencies {
    compile gradleApi()
    compile localGroovy()
    compile 'com.android.tools.build:gradle:3.2.1'
}
  • [x] 配置META-INF
  1. 在src/main/下新建目录:resources/META-INF/gradle-plugins/
  2. 在这个目录下新建文件:xx.properties,这个xx就是plugin id,也就是用户使用这个插件的时候apply plugin:的那个id
  3. 在xx.properties中定义:implementation-class=[class name of your plugin]

调试自定义Plugin

如何调试自定义Plugin呢,我一般喜欢用本地repo调试,如何把你的plugin上传到本地repo呢,只需要在你的plugin模块的build.gradle中加入以下代码:

apply plugin: 'maven'

group = 'com.xxx.xxx'
version = '0.0.1'

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: uri('../repo'))
        }
    }
}

这个时候AS的gradle面板会多一个uploadArchives任务,执行这个任务便可以把plugin上传到工程根目录的repo目录下。

android-apt和annotationProcessor

作用

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,对这些Annotation进行处理。常用的处理方式包括根据这些注解自动生成一些java源文件或者java class文件。

android-apt

android-apt是annotationProcessor出现之前的apt框架。要使用android-apt需要添加如下的代码:

添加android-apt到Project下的build.gradle中

//配置在Project下的build.gradle中
buildscript {
    repositories {
      mavenCentral()
    }
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

在Module中build.gradle的配置(以dagger为例)

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    apt 'com.squareup.dagger:dagger-compiler:1.1.0'
}

annotationProcessor

annotationProcessor也是一种APT工具,他是google开发的内置框架,不需要引入,可以直接在module的build.gradle文件中使用(以butterknife为例):

dependencies {
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
}

自定义注解处理器

创建一个java module,编写一个类,继承AbstractProcessor。并且重写其中的process方法:

public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        ...
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        ...
        return true;
    }
    
    @Override
    public Set getSupportedAnnotationTypes() {
        Set annotataions = new LinkedHashSet();
        annotataions.add(MyAnnotation.class.getCanonicalName());
        return annotataions;
    }
    
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

Export Processor

有两种方法Export Processor:

手动暴露
  1. 在 processors 库的 main 目录下新建 resources 资源文件夹
  2. 在 resources文件夹下建立 META-INF/services 目录文件夹
  3. 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件
  4. 在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径
使用AutoService

AutoService注解处理器是Google开发的,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,你只需要在你定义的注解处理器上添加 @AutoService(Processor.class) 就可以了,简直不能再方便了。

  • [x] 添加依赖
dependencies {
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
}
  • [x] 用@AutoService注解Processor
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    // ...
}

Transform

Transform用来干嘛

还是那句话,研究任何一个新技术之前都要先弄明白这个技术是用来干嘛的,不然就毫无意义。

前面介绍了Plugin,但是apply plugin是发生在配置阶段,还没有涉及到真正的构建过程。如果我们想在构建过程中做一些事,比如我们想拿到编译时产生的Class文件,并在生成Dex之前做一些处理。

Transform就是用来应对这种场景的。

另外一种处理方式

Transform API 是在1.5.0-beta1版开始使用的。在此之前,如果我们想拿到编译时产生的Class文件,并在生成Dex之前做一些处理,常用的方式是注册project的afterEvaluate方法,在这个方法中拿到一些构建过程中的task,并在这个task中注入一些action来完成:

project.afterEvaluate {
    System.out.println(TAG + "execute afterEvaluate: " + project)
    def extension = project.extensions.findByType(AppExtension.class)
    extension.applicationVariants.all { variant ->
        String variantName = capitalize(variant.getName())
        Task mergeJavaResTask = project.tasks.findByName(
                "transformResourcesWithMergeJavaResFor" + variantName)
        System.out.println(TAG + "mergeJavaResTask: " + mergeJavaResTask)
        mergeJavaResTask.doLast {
            System.out.println(TAG + "mergeJavaResTask.doLast execute")
        }
    }
}

使用Transform

定义Transform

自定义Transform,继承自Transform类:

class AgsTransform extends Transform {

    final String TAG = "[AgsTransform]"

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        System.out.println(TAG + "start transform")
        super.transform(transformInvocation)
    }

    @Override
    String getName() {
        return AgsTransform.simpleName
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return false
    }
}
输入的类型
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
    return TransformManager.CONTENT_CLASS
}

输入类型有两种,CLASSES和RESOURCES,在DefaultContentType指定:

enum DefaultContentType implements ContentType {
    /**
     * The content is compiled Java code. This can be in a Jar file or in a folder. If
     * in a folder, it is expected to in sub-folders matching package names.
     */
    CLASSES(0x01),

    /** The content is standard Java resources. */
    RESOURCES(0x02);

    private final int value;

    DefaultContentType(int value) {
        this.value = value;
    }

    @Override
    public int getValue() {
        return value;
    }
}

TransformManager中定义了一系列的类型集合:

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);
public static final Set<ContentType> DATA_BINDING_BASE_CLASS_LOG_ARTIFACT =
        ImmutableSet.of(ExtendedContentType.DATA_BINDING_BASE_CLASS_LOG);
输入文件所属的范围
@Override
Set<? super QualifiedContent.Scope> getScopes() {
    return TransformManager.SCOPE_FULL_PROJECT
}

getScopes()用来指明自定的Transform的输入文件所属的范围, 定义在Scope中:

enum Scope implements ScopeType {
    /** Only the project content */
    PROJECT(0x01),
    /** Only the sub-projects. */
    SUB_PROJECTS(0x04),
    /** Only the external libraries */
    EXTERNAL_LIBRARIES(0x10),
    /** Code that is being tested by the current variant, including dependencies */
    TESTED_CODE(0x20),
    /** Local or remote dependencies that are provided-only */
    PROVIDED_ONLY(0x40),

    /**
     * Only the project's local dependencies (local jars)
     *
     * @deprecated local dependencies are now processed as {@link #EXTERNAL_LIBRARIES}
     */
    @Deprecated
    PROJECT_LOCAL_DEPS(0x02),
    /**
     * Only the sub-projects's local dependencies (local jars).
     *
     * @deprecated local dependencies are now processed as {@link #EXTERNAL_LIBRARIES}
     */
    @Deprecated
    SUB_PROJECTS_LOCAL_DEPS(0x08);

    private final int value;

    Scope(int value) {
        this.value = value;
    }

    @Override
    public int getValue() {
        return value;
    }
}

同样,TransformManager中定义了一系列的Scope集合:

public static final Set<ScopeType> PROJECT_ONLY = ImmutableSet.of(Scope.PROJECT);
public static final Set<Scope> SCOPE_FULL_PROJECT =
        Sets.immutableEnumSet(
                Scope.PROJECT,
                Scope.SUB_PROJECTS,
                Scope.EXTERNAL_LIBRARIES);
public static final Set<ScopeType> SCOPE_FULL_WITH_IR_FOR_DEXING =
        new ImmutableSet.Builder<ScopeType>()
                .addAll(SCOPE_FULL_PROJECT)
                .add(InternalScope.MAIN_SPLIT)
                .build();
public static final Set<ScopeType> SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS =
        ImmutableSet.of(Scope.PROJECT, InternalScope.LOCAL_DEPS);
重写transform方法

我们可以通过TransformInvocation来获取输入,也可以获取输出的功能:

@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
    System.out.println(TAG + "start transform")
    super.transform(transformInvocation)
    //处理输入
    System.out.println(TAG + "处理输入")
    for (TransformInput input : transformInvocation.inputs) {
        input.jarInputs.parallelStream().forEach(new Consumer<JarInput>() {
            @Override
            void accept(JarInput jarInput) {
                File file = jarInput.getFile()
                JarFile jarFile = new JarFile(file)
                Enumeration<JarEntry> entries = jarFile.entries()
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement()
                    System.out.println(TAG + "JarEntry: " + entry)
                }
            }
        })
    }
    //处理输出
    System.out.println(TAG + "处理输出")
    File dest = transformInvocation.outputProvider.getContentLocation(
            "output_name",
            TransformManager.CONTENT_CLASS,
            TransformManager.PROJECT_ONLY,
            Format.DIRECTORY)
}

注册Transform

在Plugin中注册:

def extension = project.extensions.findByType(AppExtension.class)
System.out.println(TAG + extension)
extension.registerTransform(new AgsTransform())

extension

extension用于向你的Plugin中传入一些配置信息,使用起来很简单。

创建Bean

创建一个存放配置信息的bean:

public class DemoExtension {

    private boolean enable;
    private String message;

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("enable=").append(enable).append(";");
        sb.append("message=").append(message).append(";");
        return sb.toString();
    }
}

在Plugin中create extension

DemoExtension extension = project.getExtensions().create("demoConfig", DemoExtension.class);

在build.gradle中进行配置

demoConfig {
    enable = true
    message = 'hello world'
}

使用extension的一个坑

完成以上步骤之后,就可以使用extension了,但是extension有个坑,就是如果create之后立即使用,这个时候bean里面是默认值,也就是说build.gradle中的信息还没有加载进去,需要在整个gradle配置完成之后bean中才会填充上相应的值:

final DemoExtension extension = project.getExtensions().create("demoConfig", DemoExtension.class);
System.out.println(TAG + "extension: " + extension);
project.afterEvaluate(new Action<Project>() {
    @Override
    public void execute(Project project) {
        System.out.println(TAG + "extension afterEvaluate: " + extension);
    }
});

这样一来,如果你有些信息是需要在配置阶段读取到的,就不适合使用extension了。

使用properties

有些时候我们的配置信息是写在extension里面的,但是如果要在plugin中读取这些配置信息,需要在脚本Evaluate之后,如果要在配置的时候就使用配置信息,最常用的办法就是使用properties: 在build.gradle平级的位置建立一个名为gradle.properties的文件,里面是配置信息,如:

testProperty=hello

然后在Plugin的代码中用如下方式读取:

String testProperty = (String) project.property("testProperty");
Popular Gradle Projects
Popular Plugin Projects
Popular Build Tools Categories

Get A Weekly Email With Trending Projects For These Categories
No Spam. Unsubscribe easily at any time.
Plugin
Gradle
Groovy