概述
Google从 Android Gradle 1.5.0 开始,提供了Transform API。通过Transform API,允许第三方以插件的形式,在Android应用程序打包成dex文件之前的编译过程中操作.class文件。我们只要实现一套Transform,去遍历所有.class文件的所有方法,然后进行修改,再对源文件进行替换,即可以达到插入代码的目的。
Transform可以做什么
首先,我们可以先执行一次build操作,命令行会输出如下内容:
> Transform core-runtime.aar (androidx.arch.core:core-runtime:2.0.0) with AarTransform
> Transform lifecycle-livedata-core.aar (androidx.lifecycle:lifecycle-livedata-core:2.0.0) with AarTransform
> Transform lifecycle-livedata.aar (androidx.lifecycle:lifecycle-livedata:2.0.0) with AarTransform
> Transform interpolator.aar (androidx.interpolator:interpolator:1.0.0) with AarTransform
> Transform savedstate.aar (androidx.savedstate:savedstate:1.0.0) with AarTransform
> Transform lifecycle-viewmodel.aar (androidx.lifecycle:lifecycle-viewmodel:2.1.0) with AarTransform
> Transform lifecycle-runtime.aar (androidx.lifecycle:lifecycle-runtime:2.1.0) with AarTransform
> Transform versionedparcelable.aar (androidx.versionedparcelable:versionedparcelable:1.1.0) with AarTransform
> Transform cursoradapter.aar (androidx.cursoradapter:cursoradapter:1.0.0) with AarTransform
> Transform core.aar (androidx.core:core:1.3.2) with AarTransform
> Transform customview.aar (androidx.customview:customview:1.0.0) with AarTransform
也就是在构建过程中,会执行一个个的Transform。那么回到刚开始的问题,Transform可以做什么,我先列一些大家常听的,以及常见的:
- 无痕埋点:不需要侵入代码即可以对页面进行埋点,不过一般这种都是针对比较简单的case,复杂的业务场景很难通过无痕埋点处理。
- 性能监控:这个也很常见。
- 事件防抖:避免短期内多次点击按钮。
- 热修复:在方法前插入预留函数已做替换。
- …
那么Transform的操作到底是在什么时候将代码植入的呢?我们看一张google官方的打包图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jRGFYfrZ-1612442402262)(https://raw.githubusercontent.com/296777513/Picture/master/gradle_plugin/transform_1.png)]
Transform阶段就是在图中红圈的位置,也就是.class文件变成.dex文件过程进行插入的。说白了Transform就是Android官方提供给开发者在项目构建阶段由class到dex转换期间修改class文件的一套api。比较经典的应用就是字节码插桩和代码注入技术。有了这个API,我们就可以根据自己的业务需求做一些定制。
Transform使用
前面说了那么多,主要是介绍了,Transform是什么,能做什么。那么该如何使用呢?
我们先在我们build.gradle中新增一个依赖:
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//gradle sdk
implementation gradleApi()
//groovy sdk
implementation localGroovy()
//新增
implementation "com.android.tools.build:gradle:3.3.2"
}
然后新建一个MyTransform:
//注意Transform有很多路径
import com.android.build.api.transform.Transform
class MyTransform extends Transform {
@Override
String getName() {
return "MyTransform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
}
getName
指定自定义 Transform 的名称,返回的是对应的Task名称
getInputTypes
可以看到这个方法是返回一个Set<QualifiedContent.ContentType>集合,其实就是返回Transform需要处理的文件类型。具体有哪些,TransformManager已经给我们提供了,我们来看一下:
public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES);
public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
public static final Set<ContentType> CONTENT_NATIVE_LIBS =
ImmutableSet.of(NATIVE_LIBS);
public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX);
public static final Set<ContentType> CONTENT_DEX_WITH_RESOURCES =
ImmutableSet.of(ExtendedContentType.DEX, RESOURCES);
类型 | 描述 |
CONTENT_CLASS | 表示需要处理 java 的 class 文件 |
CONTENT_JARS | 表示需要处理 java 的 class 与 资源文件 |
CONTENT_RESOURCES | 表示需要处理 java 的资源文件 |
CONTENT_NATIVE_LIBS | 表示需要处理 native 库的代码 |
CONTENT_DEX | 表示需要处理 DEX 文件 |
CONTENT_DEX_WITH_RESOURCES | 表示需要处理 DEX 与 java 的资源文件 |
getScopes
可以看到这个方法是返回一个Set<QualifiedContent.Scope>集合,其实就是返回Transform处理的作用域。具体有哪些,我们来看一下:
/** 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);
这里主要介绍下前面五个。
类型 | 描述 |
PROJECT | 只处理当前的项目 |
SUB_PROJECTS | 只处理子项目 |
EXTERNAL_LIBRARIES | 只处理外部依赖库 |
TESTED_CODE | 测试代码 |
PROVIDED_ONLY | 只提供本地或者远程依赖项 |
同样,TransformManager为我们分装了Scope的返回集合,具体如下:
public static final Set<ScopeType> PROJECT_ONLY = ImmutableSet.of(Scope.PROJECT);
public static final Set<Scope> SCOPE_FULL_PROJECT =
Sets.immutableEnumSet(
Scope.PROJECT,
Scope.SUB_PROJECTS,
Scope.EXTERNAL_LIBRARIES);
public static final Set<ScopeType> SCOPE_FULL_WITH_IR_FOR_DEXING =
new ImmutableSet.Builder<ScopeType>()
.addAll(SCOPE_FULL_PROJECT)
.add(InternalScope.MAIN_SPLIT)
.build();
public static final Set<ScopeType> SCOPE_FULL_WITH_FEATURES =
new ImmutableSet.Builder<ScopeType>()
.addAll(SCOPE_FULL_PROJECT)
.add(InternalScope.FEATURES)
.build();
public static final Set<ScopeType> SCOPE_FULL_WITH_IR_AND_FEATURES =
new ImmutableSet.Builder<ScopeType>()
.addAll(SCOPE_FULL_PROJECT)
.add(InternalScope.MAIN_SPLIT)
.add(InternalScope.FEATURES)
.build();
public static final Set<ScopeType> SCOPE_FEATURES = ImmutableSet.of(InternalScope.FEATURES);
public static final Set<ScopeType> SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS =
ImmutableSet.of(Scope.PROJECT, InternalScope.LOCAL_DEPS);
public static final Set<ScopeType> SCOPE_IR_FOR_SLICING =
ImmutableSet.of(Scope.PROJECT, Scope.SUB_PROJECTS);
isIncremental
是否进行增量更新,如果返回true,TransformInput会包含一份修改的文件列表,如果返回 false,则会删除上次修改的记录并进行全量编译。
transform
这是最主要的方法,对文件和jar对处理都是在这里进行的,代码植入也是通过此方法进行操作的。常用到的属性有以下几个:
- TransformInput:对输入的class文件转变成目标字节码文件,TransformInput就是这些输入文件的抽象。目前它包含DirectoryInput集合与JarInput集合。
- DirectoryInput:源码方式参与项目编译的所有目录结构及其目录下的源文件。
- JarInput:Jar包方式参与项目编译的所有本地jar或远程jar包。
- TransformOutProvider:通过这个类来获取输出路径。
使用
当你编写完成之后,我们只需要在我们的plugin中添加如下代码就可以使用你自己写的Transform了。
class MyGradlePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
...
def android = project.extensions.getByType(AppExtension)
def classTransform = new MyTransform(project)
//注册你的Transform
android.registerTransform(classTransform)
...
}
}
总结
回到标题,Transform是什么?Transform其实就是在编译过程中可以动态织入代码。最主要的目的就是解耦。让开发更注重于业务开发。一些数据监控、无痕埋点等逻辑交给Transfrom处理。
参考
Gradle-初探代码注入Transform