自定义gradle插件
来源:互联网 发布:网络二手车平台骗局 编辑:程序博客网 时间:2024/06/11 09:54
前言:还记得前期做过一个android热修复的东西,其中有一个很重要的步骤就是通过javassist对jar进行字节码修改。当初修改字节码使用的是一个jar包。今天将为修改字节码这一步骤定义成一个gradle插件。
一、gradle的工作流
1.1初始化阶段
在该阶段主要是做一些编译的前期准备工作,可以通俗的理解为解析settings.gradle
ps:
1.1.1gradle中的project
Gradle 中,每一个待编译的工程都叫一个 Project。每一个 Project 在构建的时候都包含一系列的 Task。每一个project都对应一个build.gradle,该project的task都定义build.gradle引用的插件里。
如上图所示,总共有4个project,可以这么简单理解,谁的目录下有build.gradle 谁就是一个project
1.1.2 multi-projects build
我们有过这种经历,一个工程中的app模块需要引用到一个lib模块
但是当我们执行 ./gradlew iD 来安装app应用时,其实app和lib两个模块都有编译。这种模式叫multi-projects build。
Gradle 的 Multi-Projects Build 很容易,需要:
在 posdevice 下也添加一个 build.gradle。这个 build.gradle 一般干得活是:配置 其他子 Project 的。比如为子 Project 添加一些属性。这个 build.gradle 有没有都无所 属。
在 posdevice 下添加一个名为 settings.gradle。这个文件很重要,名字必须是 settings.gradle。它里边用来告诉 Gradle,这个 multiprojects 包含多少个子 Project。
来看 settings.gradle 的内容,最关键的内容就是告诉 Gradle 这个 multiprojects 包含哪些 子 projects。
//include ':app', ':mylibrary', ':hotfix' 这种配置也可以分开写成以下方式include ':mylibrary'include ':app'include ':hotfix'
1.2配置阶段
Configration阶段的目标是解析每个project中的build.gradle。比如multi-project build 例子中,解析每个子目录中的 build.gradle,每个 Project 都会被解析,其内部的任务也会被添加到一个有向 图里,用于解决执行过程中的依赖关系
1.3 执行阶段
你在 gradle xxx 中指定什么任务,gradle 就会将这个 xxx 任务链上的所有任务全部按依赖顺序执行一遍!
ps:
task的任务链由dependsOn进行串联
task a(dependsOn: 'b'){}task b(dependsOn: 'c'){}task b(dependsOn: 'c'){}
当你执行 ./gradle a 时,其任务真正执行顺序是 c->b->a(这就是一条task任务链)
二、自定义插件(groovy)
扯了半天和主题无关的,言归正传。
2.1建立插件工程目录结构
2.1.1在电脑磁盘上新建一个文件夹
2.1.2紧接着在里面新建一个文件build.gradle(可以不需要任何内容,只需要一个空壳文件,到这里其实我们已经建立好一个gradle project)
2.1.3 通过AS工具打开刚才新建的project
在这里你可能会问,为啥不直接使用AS创建一个project,要知道as默认创建的是一个android project
2.1.4 建立项目工程结构
如图所示创建3个文件夹,请注意文件夹名字必须如上所示。
2.2 自定义插件编写
2.2.1 添加goovy依赖
我们先给这个Project添加一下依赖,因为我们最开始是通过新建文件夹的形式,然后在AS中导入这个项目,所以它还没有把groovy相关的包依赖进来。我们在项目名字上右键,选择Open Module Settings,然后添加Dependencies,如下图所示(网络盗图):
2.2.2创建类InjectPlugin(通过new->file形式新建InjectPlugin.groovy)
2.2.3实现接口 plugin
class InjectPlugin implements Plugin<Project> { @Override void apply(Project project) { }}
2.2.4定义扩展属性
说扩展属性可能叫陌生,但是当你看到下面的配置,你会恍然大悟:
android { compileSdkVersion sdkVersion //这些东西就是扩展属性Extension buildToolsVersion "23.0.2" defaultConfig { minSdkVersion 9 targetSdkVersion 23 versionCode 1 versionName "1.0" }
我们要修改jar包的字节码,因此需要从外部传入待修改jar包路径,以及操作完之后存放路径,另外还需要一个编译该jar包的android.jar路径。
创建外部属性也很简单 在ExtensionContainer有相关api
https://docs.gradle.org/current/javadoc/org/gradle/api/plugins/ExtensionContainer.html
这里有三个参数:
name - The name for the extension (该名字会在build.gradle中使用,后续会有介绍)
type - The type of the extension(这是一个类,用于接收外部出入的参数)
constructionArguments - The arguments to be used to construct the extension instance(可选参数)
在我们工程中实现起来非常方便:
//创建扩展属性 injectConfig,并将外部属性配置使用InjectPluginExtension进行管理project.extensions.create("injectConfig", InjectPluginExtension)//插件获取外部配置扩展属性roject.afterEvaluate {//这里获取扩展属性时的名字,同上面定义的名字保持一致 extension = project['injectConfig']; jarpath = extension.jarDir outputdir = extension.outputDir androidhome = extension.androidHome }
现在我们来建立InjectPluginExtension.groovy
package com.inject.ndhclass InjectPluginExtension { String outputDir;//操作结束后jar存放路径 String jarDir;//待操作jar路径 String androidHome//编译该jar的android.jar路径}
外部build.gradle配置扩展属性是这样子的:
//这里的injectConfig同创建扩展属性的name参数名字保持一致injectConfig{ //注入成功后 jar包存放位置 outputDir "${basedir}/mylibrary/out" //待注入的jar包 jarDir "${basedir}/mylibrary/build/libs/mylib.jar" //当前编译环境下的android.jar路径 androidHome "/Applications/AS/dv/android-sdk/platforms/android-${sdkVersion}/android.jar"}
2.2.5 创建task
InjectPlugin–>apply方法中定义:
project.task("inject") << { createDir(project) initJar() reBuildJar(project) }
以上就定义了一个名为inject的task,当外部build.gradle引用了该插件后,通过./gradlew inject 就可以执行这里的方法。
createDir(project)方法:
def createDir(Project project) { project.exec { commandLine "mkdir", "-p", "${outputdir}" } }
这里表示通过命令行创建一个文件夹。其中的exec你可能会好奇是什么东西,当你看看前面一片gradle入门介绍里的taskType简介(http://blog.csdn.net/killer991684069/article/details/51767222)之后相信你会豁然开朗。在project里面有所有gradle原生taskType相关的api可以调用。
injectJar()几乎是纯java风格的代码,这里不做解释,有兴趣请看实例代码,代码下载地址在文章最后。
reBuildJar()主要执行jar重编译(javassist操作完字节码只会输出其class文件,我们需要将class文件重新打包成jar),以及将编译后的jar拷贝到指定目录:
def reBuildJar(Project project) { println("${desJarName},${outputdir}") project.exec { //执行jar命令 进行打包 commandLine "jar", "-cvfM", "${desJarName}", "-C", "${outputdir}", "." } project.exec { //执行mv命令,将编译后的jar包 移动到指定的输出目录 commandLine "mv", "${desJarName}", "${outputdir}" } }
2.2.6 hook已存在的task
这里我想在别人使用我的插件的使用,只要执行完build task,我就让它立即进行jar的字节码修改:
//获取build任务project.tasks.getByName("build") {//build任务执行完后立即执行jar的注入 也即是:你通过命令执行./gradlew build 编译好的jar包会立即被修改字节码 doLast { println("START INJECT1!!" + jarpath) createDir(project) initJar() reBuildJar(project) } }
三、自定义插件发布
3.1 给插件命名
在发布插件时,得先命名好我们的插件,以便外部引用
在META-INF.gradle-plugins下新建一个xxx.properties(图中的xxx是com.inject.ndh),xxx就是我们编写的插件的名字(上图表示插件的名字叫com.inject.ndh)。这里有一点需要注意,xxx一定不能同外部扩展属性同名(具体原因我没有搞明白),在xxx.properties里面配置好该插件的实现类:
// 等号 (=)左边是固定写法,右边是插件的实现类的全类名 implementation-class = com.inject.ndh.InjectPlugin
3.2 插件发布
编写前期定义的build.gradle文件
apply plugin: 'groovy'apply plugin: 'maven'//定义我们插件的版本号version = '1.0.3'//为我们的插件分组group = 'com.inject.ndh'archivesBaseName = 'inject'repositories { mavenCentral()}dependencies { compile gradleApi() compile localGroovy() // 插件工程中 使用到了javassist compile 'org.javassist:javassist:3.18.+'}// 一定要记得使用交叉编译选项,因为我们可能用很高的JDK版本编译,为了让安装了低版本的同学能用上我们写的插件,必须设定source和targetcompileGroovy { sourceCompatibility = 1.7 targetCompatibility = 1.7 options.encoding = "UTF-8"}uploadArchives { repositories.mavenDeployer {// 如果你公司或者自己搭了nexus私服,那么可以将插件deploy到上面去// repository(url: "http://10.XXX.XXX.XXX:8080/nexus/content/repositories/releases/") {// authentication(userName: "admin", password: "admin")// }// 如果没有私服的话,发布到本地也是ok的 repository(url: 'file:/Users/ndh/Desktop/mm/mm') }}
通过 ./gradlew uploadArchives 将我们的插件发布到相应的仓库,这里我发布到了本地 /Users/ndh/Desktop/mm/mm。发布完成后在仓库中可以看到:
上图看到了1.0.0~1.0.3 四个文件夹,这表示我已经发布了4个版本的插件。点开1.0.3文件夹可以看到一系列的文件(别去删了哦),
反编译看看第一个inject-1.0.3.jar是啥:
可以看到就是我们写的代码。 换句话说,groovy写的插件最终编译成了jar包,gradle引用的插件其实也是一个jar包。
3.3 外部引用该插件
3.3.1 根目录中的配置
在工程根目录的build.gradle里做如下配置
第一处 在buildscript 里配置我们的插件仓库路径(文中是发布到本地仓库)
第二处 在dependencies 引用我们的插件:这个地址由三部分组成
1 group (图中的com.inject.ndh 定义在插件工程build.gradle里 group = ‘com.inject.ndh’)
2 archivesBaseName (图中的inject定义在插件工程build.gradle里 archivesBaseName = ‘inject’)
3 版本号 (图中的1.0.3定义在插件工程build.gradle里version = ‘1.0.3’)
第三处在allprojects下配置插件仓库路径
3.3.2 实际工程中的引用
mylibrary/build.gradle中的配置
//引用插件 插件名字就是xxx.properties文件名apply plugin: 'com.inject.ndh'//配置扩展属性injectConfig{ //注入成功后 jar包存放位置 outputDir "${basedir}/mylibrary/out" //待注入的jar包 jarDir "${basedir}/mylibrary/build/libs/mylib.jar" //当前编译环境下的android.jar路径 androidHome "/Applications/AS/dv/android-sdk/platforms/android-${sdkVersion}/android.jar"}
四、实例下载
自定义插件工程地址:https://github.com/killer8000/Inject_gradle_plugin
插件引用工程地址下载:https://github.com/killer8000/HotFix_SDK2
- Gradle自定义插件
- Gradle自定义插件
- 自定义gradle插件
- 自定义gradle插件
- Gradle自定义插件
- 自定义gradle插件
- Gradle自定义插件详解
- 自定义Gradle插件
- 自定义 Gradle 插件
- Gradle构建之自定义Gradle插件
- 自定义Gradle插件(一)
- 自定义Gradle插件(二)
- 自定义Gradle插件(一)
- 使用AndroidStudio自定义Gradle插件
- 自定义Gradle插件学习笔记
- 自定义Gradle插件之"Hello World"
- 在AndroidStudio中自定义Gradle插件
- gradle自定义插件与上传本地仓库
- MyEclipse 2015 智能提示 CSS3.0和HTML5的标签
- Mysql数据库引擎使用innodb还是Myisam
- java中File文件路径跨平台(File.separator)
- centos7虚拟机安装elasticsearch5.0.x-安装篇
- 基于安卓的google+ 分享
- 自定义gradle插件
- spring boot 学习笔记(005)返回json对象
- Java空字符串与null区别
- K
- WAS 日志分析(websphere application server)
- Unity3d Attribute 总结
- CentOS 6.3 下 vsftpd 匿名用户访问配置
- Kettle使用介绍
- android GPS 定位