又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记
Gradle系列(一):Groovy学习
Gradle学习系列(二):Gradle核心解密
Gradle学习系列(三):Gradle插件
Gradle学习系列(四):Gradle依赖
Gradle学习系列(五):Gradle Transform
简介Google从 AndroID Gradle 1.5.0开始提供了transform API,Gradle transform是AndroID 官方提供给开发者在项目构建阶段(class->dex期间)用来修改.class文件的一套标准API,把class文件转换为字节码,然后 *** 作字节码,目前比较常用的就是,字节码插桩技术
AndroID的打包过程
通过上图我们可以知道,我们需要在红色箭头处通过transform API拿到应用程序的class文件,然后借助AMS之类的库,对class文件中的方法进行遍历,然后找到我们需要改动的方法,修改目标方法,插入我们的代码保存,这就是字节码插桩技术
transform相关方法介绍实现一个transform需要创建一个Gradle插件,至于插件相关可以看我之前的文章Gradle学习系列(三):Gradle插件
,这里就直接从transform开始讲了
class Renxhtransform extends transform { @OverrIDe String getname() { return null } @OverrIDe Set<QualifIEdContent.ContentType> getinputTypes() { return null } @OverrIDe Set<? super QualifIEdContent.Scope> getScopes() { return null } @OverrIDe boolean isIncremental() { return false } @OverrIDe voID transform(transformInvocation transformInvocation) throws transformException, InterruptedException, IOException { super.transform(transformInvocation) }}
可以看到如果实现一个transform主要有四个方法,下面依次讲一下这四个方法
getname返回transform的名称,一个应用内可以有多个transform,因此需要一个名称进行标识,方便后面调用
那么最终的名字是怎么组成的呢?
在gradle plugin 的源码中有一个transformManager
的类,他的主要作用就是管理所有的transform子类,里面一个方法getTasknamePrefix
就是名字的规则
static String getTasknamePrefix(@NonNull transform transform) { StringBuilder sb = new StringBuilder(100); sb.append("transform"); sb.append( transform .getinputTypes() .stream() .map( inputType -> CaseFormat.UPPER_UNDERscore.to( CaseFormat.UPPER_CAMEL, inputType.name())) .sorted() // Keep the order stable. .collect(Collectors.joining("And"))); sb.append("With"); StringHelper.appendCAPItalized(sb, transform.getname()); sb.append("For"); return sb.toString(); }
名字是以transform
开头,然后拼接ContentType
这个下面详细讲,ContentType之间用add拼接,然后加上with
最后拼接getname
返回的name
获取输入类型,ContentType表示类型,我们看下源码
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; } }
这里表示输入类型包括俩种,CLASSES 和 RESOURCES
分别代表java的class文件和资源文件
这个是指transform需要处理那些输入文件,官方文档一共有7中范围
EXTERNAL_liBRARIES : 只有外部库PROJECT : 只有项目内容PROJECT_LOCAL_DEPS : 只有项目的本地依赖(本地jar)PROVIDED_ONLY : 只提供本地或远程依赖项SUB_PROJECTS : 只有子项目SUB_PROJECTS_LOCAL_DEPS: 只有子项目的本地依赖项(本地jar)TESTED_CODE :由当前变量(包括依赖项)测试的代码看下源码
enum Scope implements ScopeType { /** Only the project (module) content */ PROJECT(0x01), /** Only the sub-projects (other modules) */ 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; } }
如果范围越小,我们需要处理文件就越少,处理的速度就会越快
isIncremental表示是否支持增量编译,一个自定义的transform在可能的情况下,支持增量编译,可以节省一些编译的时间和资源
transformtransform方法的参数transformInvocation是一个接口,提供一些关于输入的基本信息,利用这些接口就可以获得编译流程中的class文件进行 *** 作
从上图可以看到,其实整体的处理流程还是很简单的,就是从transformInvocation获取输入,然后按照class文件夹和jar集合进行遍历,拿到所有的class文件,进行处理
@OverrIDe voID transform(transformInvocation transformInvocation) throws transformException, InterruptedException, IOException { super.transform(transformInvocation) printcopyRight() transformOutputProvIDer transformOutputProvIDer = transformInvocation.getoutputProvIDer() List<transforminput> inputs= transformInvocation.getinputs() transformInvocation.getinputs().each { transforminput transforminput -> transforminput.jarinputs.each { Jarinput jarinput -> println("jar=" + jarinput.name) } transforminput.directoryinputs.each { Directoryinput directoryinput -> directoryinput.getfile().eachfile { file file -> printfile(file) } } } }
transforminputtransforminput指的是输入文件的一个抽象,包括
Directoryinput集合指源码方式参与项目编译的所有目录结构以及其中的源码文件Jarinput集合
是指以jar包的形式参与项目编译的所有的本地jar包和远程jar包(包括aar)
transformOutputProvIDer指的是transform的输出,通过他可以获取输出路径信息
实战第一步首先我们要创建一个插件项目,至于插件相关可以看我之前的文章Gradle学习系列(三):Gradle插件
与上次相比,这次多了引入了 implementation 'com.androID.tools.build:gradle:3.4.1'
因为这次需要用到androID plugin中的API
创建文件Renxhtransform.groovy继承transform
package com.renxh.cuspluginimport com.androID.build.API.transform.Directoryinputimport com.androID.build.API.transform.Formatimport com.androID.build.API.transform.Jarinputimport com.androID.build.API.transform.QualifIEdContentimport com.androID.build.API.transform.transformimport com.androID.build.API.transform.transformExceptionimport com.androID.build.API.transform.transforminputimport com.androID.build.API.transform.transformInvocationimport com.androID.build.API.transform.transformOutputProvIDerimport com.androID.build.gradle.internal.pipeline.transformManagerimport com.androID.utils.fileUtilsimport org.gradle.API.Projectclass Renxhtransform extends transform { Project mProject Renxhtransform(Project project) { this.mProject = project; } @OverrIDe String getname() { return "Renxhtransform" } /** * 需要处理的数据类型,有两种枚举类型 * CLASSES 代表处理的 java 的 class 文件,RESOURCES 代表要处理 java 的资源 * @return */ @OverrIDe Set<QualifIEdContent.ContentType> getinputTypes() { return transformManager.CONTENT_CLASS } /** * 指 transform 要 *** 作内容的范围,官方文档 Scope 有 7 种类型: * 1. EXTERNAL_liBRARIES 只有外部库 * 2. PROJECT 只有项目内容 * 3. PROJECT_LOCAL_DEPS 只有项目的本地依赖(本地jar) * 4. PROVIDED_ONLY 只提供本地或远程依赖项 * 5. SUB_PROJECTS 只有子项目。 * 6. SUB_PROJECTS_LOCAL_DEPS 只有子项目的本地依赖项(本地jar)。 * 7. TESTED_CODE 由当前变量(包括依赖项)测试的代码 * @return */ @OverrIDe Set<? super QualifIEdContent.Scope> getScopes() { return transformManager.ScopE_FulL_PROJECT } @OverrIDe boolean isIncremental() { return false } @OverrIDe voID transform(transformInvocation transformInvocation) throws transformException, InterruptedException, IOException { super.transform(transformInvocation) printcopyRight() transformOutputProvIDer transformOutputProvIDer = transformInvocation.getoutputProvIDer() transformInvocation.getinputs().each { transforminput transforminput -> transforminput.jarinputs.each { Jarinput jarinput -> println("jar=" + jarinput.name) processJarinput(jarinput, transformOutputProvIDer) } transforminput.directoryinputs.each { Directoryinput directoryinput -> if (directoryinput.file.isDirectory()) { fileUtils.getAllfiles(directoryinput.file).each { file file -> println(file.name) } } processDirectoryinputs(directoryinput, transformOutputProvIDer) } } } static voID printfile(file file) { if (file.isDirectory()) { file[] files = file.Listfiles() files.each { file file1 -> if (file1.isDirectory()) { printfile(file1) } else { println("file = " + file.name) } } } else { println("file = " + file.name) } } static voID printcopyRight() { println() println("******************************************************************************") println("****** ******") println("****** 欢迎使用 Renxhtransform 编译插件 ******") println("****** ******") println("******************************************************************************") println() } static voID processJarinput(Jarinput jarinput, transformOutputProvIDer outputProvIDer) { file dest = outputProvIDer.getContentLocation( jarinput.getfile().getabsolutePath(), jarinput.getContentTypes(), jarinput.getScopes(), Format.JAR) // to do some transform // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了 fileUtils.copyfile(jarinput.getfile(),dest) } static voID processDirectoryinputs(Directoryinput directoryinput, transformOutputProvIDer outputProvIDer) { file dest = outputProvIDer.getContentLocation(directoryinput.getname(), directoryinput.getContentTypes(), directoryinput.getScopes(), Format.DIRECTORY) // 建立文件夹 fileUtils.mkdirs(dest) // to do some transform // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了 fileUtils.copyDirectory(directoryinput.getfile(),dest) }}
可以看到其实最主要的代码在transform()
方法中,上面主要是打印了我们自定义的文本,然后分别遍历directory和jar
包然后打印出他们的名字,这里没有对文件进行处理,最后把输入文件拷贝到目标目录下,需要注意的是即使我们没有对文件做任何处理,我们仍然需要把输入文件拷贝到目标目录下,否则下一个Task就没有Tansforminput,如果我们将input目录复制到output指定目录,会导致最后的打包的apk缺少class
在插件中注册自定的transform
class CustomPlugin implements Plugin<Project> { @OverrIDe voID apply(Project project) { AppExtension appExtension = project.getExtensions().findByType(AppExtension.class) appExtension.registertransform(new Renxhtransform(project)) }
app 的 build.gradle 里我们通常会采用插件 apply plugin: ‘com.androID.application’ ,而在 library module 中则采用插件 apply plugin: ‘com.androID.library’,我们看下Gradle的源码com.androID.application
插件对应的实现类是AppPlugin
类
AppPlugin
中添加Extension
就是AppExtension
,源码如下,主要看createExtension
public class AppPlugin extends BasePlugin implements Plugin<Project> { @Inject public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) { super(instantiator, registry); } protected BaseExtension createExtension(Project project, Projectoptions projectoptions, Instantiator instantiator, AndroIDBuilder androIDBuilder, SdkHandler sdkHandler, namedDomainObjectContainer<BuildType> buildTypeContainer, namedDomainObjectContainer<ProductFlavor> productFlavorContainer, namedDomainObjectContainer<SigningConfig> signingConfigContainer, namedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelinfo extraModelinfo) { return (BaseExtension)project.getExtensions().create("androID", AppExtension.class, new Object[]{project, projectoptions, instantiator, androIDBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelinfo}); } public voID apply(Project project) { super.apply(project); } //省略...}
这里添加了androID
的Extension
实现类是AppExtension
,我们的transform最后添加到了AppExtension
的基类BaseExtension
的容器中了
执行 终端执行./gradlew app:assemble
命令
已经达到想要的效果
参考Android Gradle Transform 详解
Gradle Transform + ASM 探索
总结以上是内存溢出为你收集整理的Gradle学习系列(五):Gradle Transform全部内容,希望文章能够帮你解决Gradle学习系列(五):Gradle Transform所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)