TransformAPI是什么

从 1.5.0-beta1 开始,Gradle 插件包含一个 Transform API,允许 3rd 方插件在将编译的类文件转换为 dex 文件之前对其进行操作。(API 在 1.4.0-beta2 中存在,但在 1.5.0-beta1 中已完全修改)
此 API 的目标是简化注入自定义类操作而无需处理任务,并为操作的内容提供更大的灵活性。内部代码处理(jacoco、progard、multi-dex)在 1.5.0-beta1 中已经全部转移到这个新机制。
注意:这仅适用于 javac/dx 代码路径。Jack 目前不使用此 API。

以上是Android官方对于TransformAPI的介绍,地址在这里

简单来说,TransformAPI可以让我们在编译打包安卓项目时,在源码编译为class字节码后,处理成dex文件前,对字节码做一些操作。

编写Transform

本博文通过编写一个TransformAPI实例来介绍如何在Android项目中使用TransformAPI,下面就跟着本文一起来实现吧:

  1. 使用Android Studio创建Android项目,这里我取名为TransformDemo

  2. 使用buildSrc的方式创建一个gradle插件,如果你对这种方式不了解的话,可以参考我的上一篇博文:Android Gradle插件开发基础 创建好的插件目录结构如下:

  3. buildSrc目录下创建build.gradle文件,加入如下代码:

    apply plugin: 'groovy'
    apply plugin: 'maven'repositories {google()mavenCentral()
    }dependencies {implementation gradleApi()implementation localGroovy()implementation 'com.android.tools.build:gradle:4.2.2'
    }sourceSets {main {java {srcDir 'src/main/java'}resources {srcDir 'src/main/resources'}}
    }java {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8
    }
    
  4. 在插件包com.test.plugin中创建一个MyPlugin类,代码如下:

    package com.test.plugin;import com.android.build.gradle.BaseExtension;import org.gradle.api.Plugin;
    import org.gradle.api.Project;public class MyPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) {// 将Transform注册到插件中,执行插件时就会自动执行该TransformBaseExtension ext = project.getExtensions().findByType(BaseExtension.class);if (ext != null) {ext.registerTransform(new MyTransform());}}
    }
    
  5. 创建MyTransform类,代码如下,具体的代码解释放到后面:

    package com.test.plugin;import com.android.build.api.transform.QualifiedContent;
    import com.android.build.api.transform.Transform;
    import com.android.build.api.transform.TransformException;
    import com.android.build.api.transform.TransformInvocation;
    import com.android.build.gradle.internal.pipeline.TransformManager;import java.io.IOException;
    import java.util.Set;public class MyTransform extends Transform {@Overridepublic String getName() {// 最终执行时的任务名称为transformClassesWithMyTestFor[XXX] (XXX为Debug或Release)return "MyTest";}@Overridepublic Set<QualifiedContent.ContentType> getInputTypes() {return TransformManager.CONTENT_CLASS;}@Overridepublic Set<? super QualifiedContent.Scope> getScopes() {return TransformManager.SCOPE_FULL_PROJECT;}@Overridepublic boolean isIncremental() {return false;}@Overridepublic void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {super.transform(transformInvocation);System.out.println("Hello MyTransform...");}
    }
  6. 记得在src/main/resources目录下注册插件,注册方法就不详细说了,上一篇里有详细记录:Android Gradle插件开发基础

  7. 在app module下的build.gradle文件中引用创建的插件,引用方法是直接在文件头加入该配置:apply plugin: 'com.test.plugin'

  8. 然后我们使用Gradle的sync工具同步一下,可以看到日志里输出如下:

    上图中的Hello MyTransform...的日志就是我们写在MyTransform类的transform方法中打印的。

以上Demo的源码可以在这里下载:https://github.com/yubo725/android-transform-demo

TransformAPI详解

以上通过一个简单的例子展示了在Android项目中如何使用TransformAPI,然而你可能并不了解Transform的详细用法,并且也不知道TransformAPI的具体应用在什么地方,下面我整理了一份详细的TransformAPI的用法:

Transform是一个抽象类,其源码中对Transform做了详细的注释,翻译成中文表述如下:

处理中间构建工件的转换。
对于每个添加的转换,都会创建一个新任务。添加转换的操作负责处理任务之间的依赖关系。这是基于转换过程完成的。转换的输出可以被其他转换使用,并且这些任务会自动链接在一起。
转换表明它适用于什么(内容、范围)以及它生成什么(内容)。
转换接收输入作为集合 TransformInput,它由 JarInputs 和 DirectoryInputs 组成。两者都提供有关与其特定内容相关联的 QualifiedContent.Scopes 和 QualifiedContent.ContentTypes 的信息。
输出由 TransformOutputProvider 处理,它允许创建新的自包含内容,每个内容都与自己的范围和内容类型相关联。 TransformInput/Output 处理的内容由转换系统管理,它们的位置是不可配置的。
最佳做法是写入与转换接收到的 Jar/文件夹输入一样多的输出。将所有输入组合成单个输出可防止下游转换处理有限的范围。
虽然可以通过文件扩展名区分不同的内容类型,但对于 Scopes 则无法这样做。因此,如果转换请求一个范围,但唯一可用的输出包含的范围多于请求的范围,则构建将失败。
如果转换请求单个内容类型但唯一可用的内容包括比请求的类型更多的内容,则输入文件/文件夹将包含所有类型的所有文件,但转换应仅读取、处理和输出类型它要求。
此外,变换可以指示次要输入/输出。这些不由上游或下游转换处理,也不受由转换处理的类型的限制。他们可以是任何东西。
由每个转换来管理这些文件的位置,并确保在调用转换之前生成这些文件。这是通过注册转换时的附加参数来完成的。
这些辅助输入/输出允许转换读取但不处理任何内容。这可以通过让 getScopes() 返回一个空列表并使用 getReferencedScopes() 来指示要读取的内容来实现。

实现自定义的Transform一般要复写如下几个方法,下面对每个方法做一下详细解释:

getName()

getName()方法用于指明自定义的Transform的名称,在gradle执行该任务时,会将该Transform的名称再加上前后缀,如上面图中所示的,最后的task名称是transformClassesWithXXXForXXX这种格式。

getInputTypes()

用于指明 Transform 的输入类型,可以作为输入过滤的手段。在 TransformManager 类中定义了很多类型:

// 代表 javac 编译成的 class 文件,常用
public static final Set<ContentType> CONTENT_CLASS;
public static final Set<ContentType> CONTENT_JARS;
// 这里的 resources 单指 java 的资源
public static final Set<ContentType> CONTENT_RESOURCES;
public static final Set<ContentType> CONTENT_NATIVE_LIBS;
public static final Set<ContentType> CONTENT_DEX;
public static final Set<ContentType> CONTENT_DEX_WITH_RESOURCES;
public static final Set<ContentType> DATA_BINDING_BASE_CLASS_LOG_ARTIFACT;

getScopes()

用于指明 Transform 的作用域。同样,在 TransformManager 类中定义了几种范围:

// 注意,不同版本值不一样
public static final Set<Scope> EMPTY_SCOPES = ImmutableSet.of();
public static final Set<ScopeType> PROJECT_ONLY;
public static final Set<Scope> SCOPE_FULL_PROJECT; // 常用
public static final Set<ScopeType> SCOPE_FULL_WITH_IR_FOR_DEXING;
public static final Set<ScopeType> SCOPE_FULL_WITH_FEATURES;
public static final Set<ScopeType> SCOPE_FULL_WITH_IR_AND_FEATURES;
public static final Set<ScopeType> SCOPE_FEATURES;
public static final Set<ScopeType> SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS;
public static final Set<ScopeType> SCOPE_IR_FOR_SLICING;

常用的是 SCOPE_FULL_PROJECT ,代表所有 Project 。

确定了 ContentType 和 Scope 后就确定了该自定义 Transform 需要处理的资源流。比如 CONTENT_CLASS 和 SCOPE_FULL_PROJECT 表示了所有项目中 java 编译成的 class 组成的资源流。

isIncremental()

指明该 Transform 是否支持增量编译。需要注意的是,即使返回了 true ,在某些情况下运行时,它还是会返回 false 的。

transform(TransformInvocation transformInvocation)

一般在这个方法中对字节码做一些处理。

TransformInvocation是一个接口,源码如下:

public interface TransformInvocation {/*** Returns the context in which the transform is run.* @return the context in which the transform is run.*/@NonNullContext getContext();/*** Returns the inputs/outputs of the transform.* @return the inputs/outputs of the transform.*/@NonNullCollection<TransformInput> getInputs();/*** Returns the referenced-only inputs which are not consumed by this transformation.* @return the referenced-only inputs.*/@NonNull Collection<TransformInput> getReferencedInputs();/*** Returns the list of secondary file changes since last. Only secondary files that this* transform can handle incrementally will be part of this change set.* @return the list of changes impacting a {@link SecondaryInput}*/@NonNull Collection<SecondaryInput> getSecondaryInputs();/*** Returns the output provider allowing to create content.* @return he output provider allowing to create content.*/@NullableTransformOutputProvider getOutputProvider();/*** Indicates whether the transform execution is incremental.* @return true for an incremental invocation, false otherwise.*/boolean isIncremental();
}

我们既可以通过TransformInvocation来获取输入,同时也获得了输出的功能,举个例子:

public void transform(TransformInvocation invocation) {for (TransformInput input : invocation.getInputs()) {input.getJarInputs().parallelStream().forEach(jarInput -> {File src = jarInput.getFile();JarFile jarFile = new JarFile(file);Enumeration<JarEntry> entries = jarFile.entries();while (entries.hasMoreElements()) {JarEntry entry = entries.nextElement();//处理}}
}

后续文章中我会记录Gradle插件+TransformAPI+字节码插桩工具结合使用的相关应用。

参考

  • Gradle Transform API 的基本使用
  • Gradle 学习之 Android 插件的 Transform API
  • Transform详解

Android Transform API的使用相关推荐

  1. Android Transform API 从原理到实战

    Transform API 从 1.5.0-beta1 开始,Gradle 插件包含一个 Transform API,允许第三方插件在将已编译的类文件转换为 dex 文件之前对其进行操作.(该 API ...

  2. gradle-com.android.build.api.transform.TransformException:Error while generating the main dex list

    问题 What went wrong: Execution failed for task >':app:transformClassesWithMultidexlistForYm1000001 ...

  3. java 奇门遁甲代码_奇门遁甲之Transform API

    函数插桩技术是可以提高开发者开发效能的有力工具.常用的组合是TransformApi+ ASM,在打包apk的过程中,对特定的类最修改,偷梁换柱,以满足我们的一些特殊需要,如全局监控网络.计算方法耗时 ...

  4. Android Transform

    Transform详解 深入了解TransformApi 如何理解 Transform API Gradle 学习之 Android 插件的 Transform API Android Gradle ...

  5. Error:Execution failed for task ':app:transformClassesWithDexForDebug'. com.android.build.api.tran

    错误造成的原因: 为了连接网络,我导入的一个Xutils的jar包,然后发现还是还是有原理的老问题,编译版本太高了,需要相应的moudle中增加 在相应的module下的build.gradle中加入 ...

  6. 【我的ASM学习进阶之旅】 介绍一个基于gradle transform api和ASM的字节码插件平台ByteX

    原文链接: https://github.com/bytedance/ByteX/blob/master/README_zh.md 文章目录 ByteX(Infinite Possibilities) ...

  7. Android的API与差异化之路

              Android的API与差异化之路                 发挥Android特色:框架API和开源(开放)     Android平台就如同×××长城般,两岸的硬件业厂商 ...

  8. Android 中文API (94) —— MediaController

    前言 本章内容是android.widget.MediaController,版本为Android 2.3 r1,翻译来自"唐明",再次感谢"唐明" !期待你一 ...

  9. Android 中文 API 文档 (45) —— AbsoluteLayout.LayoutParams

    前言 本章内容是 android.widget.AbsoluteLayout.LayoutParams,版本为Android 2.2 r1,翻译来自"绵白糖",再次感谢" ...

最新文章

  1. Microbiome: 黄龙病破坏柑橘根部相关微生物菌群从根际到根面的富集过程
  2. grep与正则表达式
  3. Nginx配置——搭建 Nginx 高可用集群(双机热备)
  4. 如何搭建html运行环境,搭建基于express框架运行环境的方法步骤
  5. springJAR包和配置文件
  6. 用户选购计算机可分为,助理电子商务师考试试题(1+答案)
  7. expect switch 多条件_JavaScript-流程控制语句:选择结构(if和switch)
  8. 理解ORACLE数据库字符集
  9. 汇顶科技2021秋招笔试
  10. golang 安全的tcp server_Go 语言使用 TCP_NODELAY 控制发包流量
  11. POJ1149 最大流(Isap)
  12. vue-cli3.x正确打包项目,解决静态资源与路由加载无效的问题,history模式下配合使用nginx运行打包后的项目
  13. c#如何调用php接口方法参数类型,c# – .NET:使用通用接口参数调用Assembly obj的GetInterface方法...
  14. Java 拦截器自定义(添加响应头)
  15. 新版Ds社区源码(云商城1.0)
  16. Chart控件,chart、Series、ChartArea曲线图绘制的重要属性介绍(Windows窗体)
  17. NBA不可能被破的十个记录
  18. Java面试之Java基础5——面向对象的三大特性之封装、继承和多态
  19. [CF1132D]Stressful Training
  20. C语言题目:数字金字塔(有条件的老师同学点一下赞呀)

热门文章

  1. CTP的三次握手和四次断开
  2. 基于java开发的健身器材电商管理系统.rar(含项目源码前后端项目)
  3. power Apps
  4. 关于浏览器打不开视频
  5. SurfaceFlinger草稿
  6. datax从gbase8a同步上亿大表到mysql5.7中
  7. 聊天系统 数据库功能表结构如何设计?
  8. 2023年1月1日生效:2023年火车高铁儿童票最新规则及高铁火车2023儿童票怎么购买?
  9. 如何理解bottleNeck
  10. 一周AI要闻|AI博士年薪涨到80万 英伟达中国秀肌肉 特斯拉降价