Gradle构建实战:Java应用程序和库、Android应用程序构建

2021年3月8日16:25:18 发表评论 1,018 次浏览

本文概述

前言

Gradle构建实战

Gradle非常复杂!但是开发要用,我还是觉得要尽量弄明白它,本文打算讨论Gradle在实际项目开发中的使用。Gradle常用的项目类型包括Java和Android,其它则比较少,虽然Gradle也支持其它语言的项目。

尽管Gradle比Maven灵活,但是给出一个配置文件,我还是一脸懵逼,大概是来自以前的恐惧感。我以前开发Android项目的时候尝试调用C/C++,编译、打包,别说有多头痛,搞来搞去它就是进不去APK里,直接增加了我的心理阴影面积。

我们使用Gradle的最终唯一的目标是打包整个项目,把所有项目必须用到的乱七八糟的文件送进这个包里,保证程序运行起来不会缺少必要的文件。这个目标对所有构建工具都是一样的,包括Maven和make、cmake。

Gradle工作的必要流程

Gradle的主要任务是将项目的所有文件打包到最终包中,其主要流程包括:

  • 编译源代码,得到字节码文件集。
  • 处理资源文件,得到资源文件集。
  • 将字节码文件集合资源文件集归档,也就是打包为Jar或APK。

你可以看到,项目的文件主要分为源代码文件和资源文件,使用Gradle开发一般是建议遵守Gradle的项目约定,例如源码文件应该放main/java、资源文件应该放在main/resources中。但是这仅仅是约定,有时我们可能是根据习惯放置文件,却违反了约定——这是普遍情况,所以,这里对使用Gradle开发的要求是:能够使用Gradle处理所有的打包情况。

另外要说明的是:使用第三方插件打包项目,该插件一般也是遵守Gradle的项目约定的,所以不要指望这些插件可以为你打包任意文件,还是要自己动手。

Build目录文件夹介绍

Build目录用于存放项目的生成文件,其中的目录作用如下:

  • Classes:存放项目编译生成的字节码文件,javac编译的所有java文件都放到这里,这些文件会从main下的包开始打包到Jar中。
  • Distributions:生成项目分发包,一般是zip或tar,其中包含项目的主要包,可能还包括一些命令行脚本,用于快速运行程序。
  • Generated:根据资源生成的Java类。
  • Libs:Java应用最终包放在这里。
  • Resources:项目资源文件,main下的所有文件都会被打包到Jar中。
  • Tmp:编译日志。
  • Intermediates:中间过程。
  • Outputs:输出结果,Android Studio在这里会生成最终的APK。

由上面的介绍,我们可以知道,对于Java项目,其classes和resources中的文件都会被打包到Jar中。对于Android则比较复杂,但一般中间生成的文件放在intermediates目录中。

项目打包的处理逻辑

既然我们知道对应的文件会被归档,那么原则上,只要我们把对应的文件复制都指定目录就行了,打包的交给插件的任务就行了——这是其中一个逻辑,如果你实在想不到什么办法解决打包的问题,可以考虑使用这个。

另一个方法是使用implementation/compile,告诉Gradle我们需要依赖指定的文件,使用implementation指定的文件一般都会被打包进去。

还有一个方法是:自己编写编译和文件处理逻辑,或自己编写插件进行处理。本人不推荐这个方法,一来实现起来有些难度,二来一般插件都已经实现了的。

这是我目前所知道的几种方法,如果大家还知道其它的可以分享一下,Gradle相关参考文档应该有提供,但是这文档太强大了,不是一天可以看得了的。

Gradle任务和插件

任务是Gradle中的最基本处理单元,而插件可以看做是任务的合集(可能还包括一些属性),例如常见的compile、assemble、build等都是Gradle任务。每个任务完成一些指定的处理逻辑,每个任务有其对应的delegate任务对象,我们可以在任务中调用delegate对象的属性(配置)和方法(实现指定逻辑)。

对于定义任务,若任务没有指定类型,则默认为Task类型的任务,如果指定任务类型,则有可能不同。所以要注意查看Gradle官方的API文档,任务的闭包其delegate一般就是指定类型的对象。

使用Gradle构建的第一个问题:你要干什么?

这是一个很重要的问题,Gradle很强大,但是我们不是为了配置而配置,我们只配置我们需要的,就像编写项目一样,也不是为了编写代码而写程序,是需要先设计需求的。使用Gradle也是同理:明确你要做什么,不要多想,如果有新的需求有新的问题,再进一步解决。

虽然你可能见过写得很复杂的Gradle脚本,但是目前也有很多很好的插件,这些插件可以帮助我们快速解决指定的问题,所以不要慌,不要搞得那么复杂(这也是在告诉我自己)。

推荐使用IDE进行开发,一般IDE已经帮我们生成了Gradle的基本脚本,例如IDEA和Android Studio,我们只需要在其基础上进行开发即可。

按照我以前的经验,一般会有哪些问题呢?

  • Java打包可执行Jar缺少资源或依赖,以前可是很头痛,疯狂地搞一天都搞不定(别说我有多恐惧Gradle了)。
  • C/C++编译和打包的配置,这个也是很头痛的问题,同样也是打不进.a静态库和.so动态库,超级喷血!

其它则是一些小问题,例如将本地文件打包进Jar。所有这些问题,通常发生在Gradle插件的各个构建阶段,例如可能发生在编译、处理资源文件或打包阶段。而解决打包问题的一个方法是:在打包之前,新建一个Copy类型的任务,将打不进的文件复制进build的指定目录中,然后让打包任务依赖该任务,这样就可以解决问题了——果然还是要学一下Groovy和Gradle。

Gradle打包Java可执行应用程序

构建Java可执行程序一般是使用’applicaiton’插件,而不是’java’插件,Gradle官方对Java插件的描述为:

Java插件将Java编译以及测试和捆绑功能添加到项目中。它是许多其他JVM语言Gradle插件的基础。你可以在构建Java项目一章中找到关于Java插件的全面介绍和概述。

如上所述,这个插件为处理JVM项目添加了基本的构建块。它的特性集已经被其他插件所取代,提供更多基于项目类型的特性。您不应该直接将其应用到项目中,而应该研究java-library或application插件,或受支持的替代JVM语言之一。

这里以IDEA为例子,IDEA默认生成build.gradle构建脚本,生成的配置可能使用的是java插件,把它改成application即可。

先新建一个Java项目,添加一些项目必须的依赖,使用implementation指定。

在IDEA右边找到Gradle的操作面板(当然你也可以使用命令行,但不及这个方便),右键执行build或distZip任务,这些任务负责编译和打包项目:

Gradle快速构建窗口

项目的最终生成包在build/libs下面,这里面是一个Jar文件,一般只有一个。

你可以使用java -jar尝试运行,或使用解压软件打开,若无意外,你会发现运行不了该Jar或在Jar包中找不到需要的依赖字节码文件。

那么这个问题该如何解决呢?这里介绍我本人在用的方式,使用第三方插件将项目打成一个Fat Jar(包含所有必要文件),首先配置buildscript和plugins:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:6.1.0'
    }
}

plugins {
    id 'com.github.johnrengelman.shadow' version '6.1.0'
    id 'application'
}

然后指定主类位置,其设置位置在project下:

mainClassName = 'com.gui.App'

这样问题就解决了,如果你需要更多配置,可以参考当前插件支持的所有任务,把任务调出来对任务进行配置——但还是那句话,若无必要就不要多手,最简的就是最好的。

Gradle构建Java库

和构建Java可执行应用程序不同,构建Java库可以不需要打包第三方依赖,关键是java-library并没有提供部署的插件,所以这里的问题是:如何将构建的Jar部署到本地仓库或远程仓库。

由于我们只是想重用这个库,如果你不懂,大可直接到处复制进行使用。能添加进仓库当然最好了,在其它项目添加一个依赖就可以用了。另外,部署到本地或远程仓库的逻辑无非是:将库文件复制到仓库中,以及添加一些描述信息。

问题又来了:添加到哪个仓库?gradle本身缓存依赖artifacts到~/.gradle/caches/modules-2/files-2.1中(刚看了下,占了好多空间),另外还有maven仓库,要注意的是,gradle这个目录只是缓存,将库发布到maven仓库(本地或远程)才是最正式的:供其它gradle或maven项目使用。

下面来解决这个问题,如何实现?自己编写逻辑?别傻了,用插件吧,这不,Gradle就提供了一个Maven发布插件,首先添加本地maven仓库,然后添加对应的插件ID,如下:

buildscript {
    repositories {
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath 'org.apache.commons:commons-io:1.3.2'
    }
}

plugins {
    id 'java-library'
    id 'maven-publish'
}

然后配置一些基本的信息,如groupid、artifactId和version,如下:

publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'com.once'
            artifactId = 'gradle'
            version = '1.0'

            from components.java
        }
    }
}

最后执行发布任务:publishToMavenLocal,这样就发布成功了。

更多部署的配置或配置远程仓库,请参考官方文档:https://docs.gradle.org/current/userguide/publishing_maven.html。不得不说,这些文档超级长,忍不住我就复制了,都顾不上看了。

Gradle构建Android应用程序

这部分就不再写了,和上面Java可执行应用程序和库的处理方式差不多,若有可能,多些查看官方的文档,包括DSL文档:https://google.github.io/android-gradle-dsl/3.4/。

我本人也还没看过多少参考文档,但是多查看参考文档确实会逐渐增加经验,多看多用就是了,但是也不要忘记主要的精力应该的花在项目的需求实现上(好一个构建工具!)。

总结

本文主要讨论了Gradle构建实际项目的一些方法和示例,主要参考一些博客,以及官方的参考文档。Gradle用起来可不大轻松,需要Groovy和Gradle的基础,例如参考DSL文档的时候,根据需要解决的问题,查看主要类或配置块,一般是使用一个闭包进行处理,而闭包中可以调用什么属性和方法,需要闭包看所在的delegate(如Task)和全局对象(如project)。

好了,关于Gradle的学习就到这里了,对象构建日常Java项目,我觉得Gradle还是不如Maven的,Maven虽然没有Gradle那么灵活,但是它简单直接。Gradle太复杂了,作为一个配置脚本,老实说,作为一个开发者,我并不想在构建脚本上花费太多时间,期望将来有哪个大神开发一个简单直接、没有那么多学习成本的构建工具吧——所以我并不会使用Gradle开发Java项目,只是Android开发中用到而已,仅仅用于Android开发。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: