使用Gradle编译Java工程入门篇
发布时间:2025-05-23 21:39:51 发布人:远客网络
一、使用Gradle编译Java工程入门篇
通过一个简单的HelloWorld Java项目,学会如何使用Gradle来编译打包我们的工程项目
推荐使用安装器安装,比如mac下使用 Homebrew安装($ brew update&& brew install gradle),基于Unix的系统可使用 SDKMAN安装($ sdk install gradle 4.0.1),具体安装方法和步聚请参考官网安装地址: gradle.org/install/
为了测试Gradle是否安装成功,我们可以在命令行下执行gradle命令,其显示结果如下代表安装成功:
为了更好地演示如何使用Gradle脚本来编译我们的工程,我们尽量创建一个非常简单的java项目,它只包含一个HelloWorld.java文件。打开熟悉的IDE工具,创建一个HelloWorld的工程,创建步骤如下:
接下来一路点下一步,直到最后完成,我们可以看到创建的工程目录结构如下:
IDE工具会自动为我们创建很多配置文件和目录,这是使用IDE的好处,不过对于初学者来说,一上来看到这么多文件和目录可能会不知所措。我们可以先把一些文件和目录去掉,等后期熟悉了Gradle的使用后,我们再来关注其它相关配置。最后精简的工程目录结构如下(真实项目不需要精简):
图五精简后的工程文件和目录结构
精简后主要包括有三个目录,一个gradle配置文件,具体介绍如下。.gradle目录:与Gradle相关的缓存配置等内容,为隐藏文件夹.idea目录:与IDEA项目工程相关配置内容,为隐藏文件夹 src目录:Java代码目录 build.gradle配置文件:Gradle脚本配置文件
首先我们在src/main/java/目录下包名com.bill,然后再该包下创建了一个HelloWorld.java文件,其中输出打印:Hello world。
在IDEA工具中,打开Terminal终端(也可以直接打开系统终端),输入gradle tasks命令,查看当前工程中可使用的任务有哪些,如下:
从图中我们可以看到,IDEA自动为我们添加了一些默认的任务(后面有讲到如何添加的),具体任务说明如下:
在我们没有写任何gradle代码之前,我们可以先来亲自体验一下这些预添加的任务功能,我们选择build任务来编译打包该工程试下,其执行结果如下:
从图中,我们看到只要简单的一行命令,就完成了当前工程的编译打包工作,打包后会自动生成一个build文件夹,其目录结构详细介绍如下。 classes目录:存放编译后的类文件,就是.class文件 dependency-cache:存放相关依赖的缓存数据 libs:存放最终编译打好的jar包 tmp:存放编译过程中的临时文件
通过Gradle的这些任务我们可以很方便的编译打包工程,而以上这些任务又是通过添加插件来轻松实现的,先让我们看看在创建工程时,IDE工具都为我们自动添加了哪些gradle脚本内容?
相关各行代码的注释已经很明确了,其中最关键一行为apply plugin:'java',它告诉gradle使用第三方java插件,这样我们才能简单的使用gradle build命令来编译打包了,而gradle的java插件具体包括哪些任务,以及各任务之间的依赖关系,可以参考官网( docs.gradle.org/3.3/use...),其具体的关系图如下:
图八 gradle java插件任务依赖关系
再结合之前执行gradle build命令的过程(见图七),我们可以清晰的看到各任务的执行顺序(图八中箭头表明了build的调用过程):
接下来我们实现添加一个依赖包,比如使用Gson实现对json的操作。首先在maven仓库( )中搜索Gson,结果如下:
把其中compile group:'com.google.code.gson', name:'gson', version:'2.8.0'拷贝到build.gradle文件中,其完整如下:
然后在HelloWorld.java中添加如下代码:
同时添加Person.java类,如下所示:
现在我们再执行gradle build命令,看看编译后的结果是什么样?这次我们可以使用IDEA工具的可视化界面来执行build命令,如下:
编译一切正常,证明我们刚刚加入的依赖库已经被成功的引入工程中了。
大多时候,我们希望最后生成的jar包是自己指定的名称,而不是使用工程的项目名称,Gradle为我们提供了如下方式:
通过jar代码块指定了如何命令JAR文件,在本例中它将会被命令为:first-gradle-0.1.0.jar,如下图:
在用Gradle编译工程时,使用Gradle Wrapper是一种非常好的方式,它为Windows、OS X和Linux平台都提供了相应的脚本文件,这些脚本使你可以在不同平台的不同Gradle版本上正确地执行你的编译打包脚本。我们只需要执行一行命令,Gradle就会自动为我们的项目添加Wrapper相关配置信息,而不需要手动修改任何的代码配置等,其命令如下:
当执行完上面命令后,你会注意到项目中增加了一些新的文件:根目录下的两个脚本文件;一个gradle/wrapper的目录。其结构如下:
现在可以使用Gradle Wrapper编译我们的工程了,我们也能把Wrapper相关的配置信息添加到版本控制系统中,其他任何人克隆了我们的工程也能使用相同的方式编译打包该工程。
恭喜你已经成功地通过Gradle实现了一个简单的HelloWorld工程编译打包任务,在这里你了解到了Gradle的安装方法、如何使用gradle build来编译项目、如何向工程中添加依赖库、以及如何使用Gradle Wrapper编译工程。如果对Gradle感兴趣的同学,请关注我,我的新书《分布式服务架构:原理、设计与实战》也已正式登陆京东,有兴趣的可以直接购买,里面有很多平时的工作经验分享。
二、如何使用gradle创建一个简单的java工程
1、根据需要,从官网下载最新jenkins软件包,同时下载最新的gradle软件包,jenkins插件,设置好环境变量,配置好端口,启动。
2、点击新建任务输入项目名称和使用的jdk,同时还输入节点,不输入默认为master节点
3、选择源码管理工具为CVS,Git或subversion,根据自身情况而定,然后填写URL路径@HEAD
4、在构建一栏里增加构建步骤选择Invoke Gradle script,同时选择你的Gradle Version,Switches,Root Build script,对于多工程,需要添加多个Invoke Gradle script
5、上传脚本:在每个子工程的根目录下放置名为gradle.build的脚本,内容如下:
6、点击步骤1里面截图的立即构建,通过Console Output查看结果。
三、Gradle Transform + ASM 探索
1、使用 Gradle Transform+ ASM实现代码插桩的技术已经被广泛运用,本文将探索如何快速简洁地运用 Transform实现代码插桩,并尝试展示 Transform+ ASM能力的范围。简单来说,利用 AGP提供的 Transform接口,我们可以在应用打包流程中,对编译生成的 Java或 Kotlin类文件进行二次写入操作,插入自定义逻辑。这些逻辑通常与业务逻辑无关,但有助于统计应用数据的 SDK在页面展现和退出生命周期函数中插入统计相关逻辑,或实现代码耗时统计功能。
2、一些统计应用数据的 SDK在页面展现和退出的生命周期函数里,插入统计逻辑,以透明地接入,降低开发者成本,减少三方库对现有工程的耦合。而代码耗时统计功能则早在 2013年由 JakeWharton大神用 AspectJ方案实现。Transform的基本流程包括继承抽象类 Transform,覆盖几个方法来定义特定的处理逻辑。Transform可以对 resources文件进行处理,如 AndResGuard混淆资源文件,这使得开发者对 Transform的应用思路更加清晰。
3、Transform的范围定义越小,处理的输入就越少,执行速度也越快。一个工程中可能有多个 Transform,它们之间按顺序处理,每个 Transform处理的是前一个 Transform处理过的输出。所有 Transform任务通常在 app/build/intermediates/transform/目录下可见。
4、Transform的核心难点包括理解 TransformInvocation接口的参数,以及如何在 class文件处理中实现插桩逻辑。TransformInvocation提供了关于输入的基本信息,利用这些信息对编译流程中的 class文件进行操作。Transform处理输入的思路简单,主要是通过 TransformInvocation获取总的输入后,分别按照 class目录和 jar文件集合的方式进行遍历处理。
5、Transform的实践通常涉及定义注解,如 Cat注解,通过 Transform插入统计方法耗时、参数、输出的实现。例如,通过在 MainActivity中的方法上添加@Cat注解,方法执行前后调用自定义的 start和 end方法记录耗时、参数和返回值信息。然而,这样的代码写法与方法原始功能无关,且重复写多次代码不美观。因此,使用 Transform+ ASM实现代码插桩的需求出现。
6、通过定义注解 Cat,并利用 Transform处理输入,可以在方法体开始和结束之前通过 ASM插入统计逻辑。ASM提供的接口允许访问者模式,通过定义 CatClassVisitor和 CatMethodVisitor对符合条件的方法进行插桩,实现代码插桩功能。ASM代码插桩的具体实现已在其他文章中详细描述。
7、Transform还支持增量编译,通过查看 AGP自带的几个 Transform可以看到其实现相对简单。所有输入都是带有状态的,根据这些状态执行不同的处理,也可以根据 TransformInvocation提供的输入支持增量编译。
8、为简化 Transform流程,可以抽象出一个更加通用的 Transform基类,提供默认的增量编译支持和文件 IO操作的封装处理。这样,可以把精力集中在如何处理 class文件的写入和输入上,而无需再关心文件 IO和内部细节。
9、使用注解实现类中所有方法的插桩可以通过定义注解 Tiger来简化。通过直接继承 Transform抽象类,我们可以集中在处理 Class文件的写入和输入上,利用 ASM的 ClassReader和 ClassWriter接口进行交互。TigerClassVisitor的 visitAnnotation方法允许获取当前 Class的注解,而当注解与 Tiger注解相匹配时,可以在 visitMethod方法内插入耗时检测代码。
10、配置任意类中方法的插桩时,基于注解的实现适用于已有的代码。但对于需要统计开源库方法耗时的场景,可以借助 ASM简化工作,通过提供类名和方法名来确定要进行插桩的结点。在 build.gradle文件中配置信息,然后在 ClassVisitor和 MethodVisitor中根据类名和方法名确定插桩结点。
11、点击事件的统计涉及埋点,用于统计用户行为。通常实现 View.OnClickListener接口或匿名内部类,在 onClick方法中展开。确定 onClick方法是关键问题,不能仅通过方法名等于规则进行定位。这里可以利用 ClassVisitor的 visit方法提供当前类实现的所有接口来判断,确保 ASM访问的类实现了 android.view.View.OnClickListener接口。对于使用 lambda表达式实现的匿名内部类,由于不会生成额外的匿名类,无法处理。
12、总结而言,Transform+ ASM技术栈允许在构建工程的过程中,在 class文件到 dex文件直至最终的 apk文件生成之间,进行代码插桩。这些插桩功能可以用于收集关键信息,如方法耗时、页面展现时间等,为后续的数据分析和决策提供支持。通过了解 Transform+ ASM能力的范围以及实现方式,开发者可以利用这一技术栈优化应用性能和收集有价值的数据。