概述 Google从 Android Gradle 1.5.0 开始,提供了Transform API。通过Transform API,允许第三方以插件的形式,在Android应用程序打包成dex文件之前的编译过程中操作.class文件。我们只要实现一套Transform,去遍历所有.class文件的所有方法,然后进行修改,再对源文件进行替换,即可以达到插入代码的目的。
首先,我们可以先执行一次build操作,命令行会输出如下内容:
1 2 3 4 5 6 7 8 9 10 11 > 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可以做什么,我先列一些大家常听的,以及常见的:
那么Transform的操作到底是在什么时候将代码植入的呢?我们看一张google官方的打包图:
Transform阶段就是在图中红圈的位置,也就是.class文件变成.dex文件过程进行插入的。说白了Transform就是Android官方提供给开发者在项目构建阶段由class到dex转换期间修改class文件的一套api。比较经典的应用就是字节码插桩和代码注入技术。有了这个API,我们就可以根据自己的业务需求做一些定制。
前面说了那么多,主要是介绍了,Transform是什么,能做什么。那么该如何使用呢?
我们先在我们build.gradle中新增一个依赖:
1 2 3 4 5 6 7 8 9 10 dependencies { implementation fileTree(dir: "libs" , include: ["*.jar" ]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation gradleApi() implementation localGroovy() implementation "com.android.tools.build:gradle:3.3.2" }
然后新建一个MyTransform:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import com.android.build.api.transform.Transformclass 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名称
可以看到这个方法是返回一个Set<QualifiedContent.ContentType>集合,其实就是返回Transform需要处理的文件类型。具体有哪些,TransformManager已经给我们提供了,我们来看一下:
1 2 3 4 5 6 7 8 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处理的作用域。具体有哪些,我们来看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 PROJECT(0x01 ), SUB_PROJECTS(0x04 ), EXTERNAL_LIBRARIES(0x10 ), TESTED_CODE(0x20 ), PROVIDED_ONLY(0x40 ), @Deprecated PROJECT_LOCAL_DEPS(0x02 ), @Deprecated SUB_PROJECTS_LOCAL_DEPS(0x08 );
这里主要介绍下前面五个。
类型
描述
PROJECT
只处理当前的项目
SUB_PROJECTS
只处理子项目
EXTERNAL_LIBRARIES
只处理外部依赖库
TESTED_CODE
测试代码
PROVIDED_ONLY
只提供本地或者远程依赖项
同样,TransformManager为我们分装了Scope的返回集合,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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,则会删除上次修改的记录并进行全量编译。
这是最主要的方法,对文件和jar对处理都是在这里进行的,代码植入也是通过此方法进行操作的。常用到的属性有以下几个:
TransformInput:对输入的class文件转变成目标字节码文件,TransformInput就是这些输入文件的抽象。目前它包含DirectoryInput集合与JarInput集合。
DirectoryInput:源码方式参与项目编译的所有目录结构及其目录下的源文件。
JarInput:Jar包方式参与项目编译的所有本地jar或远程jar包。
TransformOutProvider:通过这个类来获取输出路径。
使用 当你编写完成之后,我们只需要在我们的plugin中添加如下代码就可以使用你自己写的Transform了。
1 2 3 4 5 6 7 8 9 10 11 12 class MyGradlePlugin implements Plugin <Project > { @Override void apply(Project project) { ... def android = project.extensions.getByType(AppExtension) def classTransform = new MyTransform(project) android.registerTransform(classTransform) ... } }
总结 回到标题,Transform是什么?Transform其实就是在编译过程中可以动态织入代码。最主要的目的就是解耦。让开发更注重于业务开发。一些数据监控、无痕埋点等逻辑交给Transfrom处理。
参考 Gradle-初探代码注入Transform