Android:hook很“危险”,使用需谨慎。
前言
上篇文章《Android安卓进阶技术分享之AGP工作原理》和大家分析了 AGP(Android Gradle Plugin) 做了哪些事,了解到 AGP 就是为打包这个过程服务的。
那么,本篇文章就和大家聊一聊其中的 Transform,解决一下为什么在 AGP 3.x.x 的版本可以通过反射获取的 transformClassesWithDexBuilderForXXX Task 在 4.0.0 的版本就不灵了?
源码走起!
Transform的流程
读本篇文章以前,相信同学们已经具备 Transform 的使用基础。
相信很多人都看过这张图:
Transform过程
正如上图中展示的,我们可以看到:
? 在一个项目中,我们可能既会有自定义的 Transform,也会有系统的 Transform。
? 在处理过程中,每一个 Transform 的接受流都是接收到上一个 Transform 的输出流,原始的文件流会经过很多 Transform 的处理。
Transform源码分析
既然我们已经了解了整体的流程,再来看一下其中的细节吧。
第一步 Transform的起点
我们都知道,使用 Transform 的目的,是为了修改其中的字节码,那么,这些 Class 文件是哪里来的呢?
直接打开 AGP 的源码,直接跳到创建编译 Task 的时候,这个方法发生在 AGP 创建跟 Variant 相关的 Task 的时候,在 AbstractAppTaskManager 里:
private void createCompileTask(@NonNull VariantPropertiesImpl variantProperties) {
ApkCreationConfig apkCreationConfig = (ApkCreationConfig) variantProperties;
// 执行javac
TaskProvider<? extends JavaCompile> javacTask = createJavacTask(variantProperties);
// 添加Class输入流
addJavacClassesStream(variantProperties);
setJavaCompilerTask(javacTask, variantProperties);
// 执行transform和dex相关的任务
createPostCompilationTasks(apkCreationConfig);
}
虽然只有几个方法,但是每个方法的作用还挺大,先看 javac。
第二步 执行javac
大家对 javac 的命令肯定很熟悉,它可以将 .java 文件转化成 .class 文件。这个方法确实也是这样:
public TaskProvider<? extends JavaCompile> createJavacTask(
@NonNull ComponentPropertiesImpl componentProperties) {
// Java预编译任务,看了一下,主要是处理Java注解
taskFactory.register(new JavaPreCompileTask.CreationAction(componentProperties));
// Java编译任务
final TaskProvider<? extends JavaCompile> javacTask =
taskFactory.register(new JavaCompileCreationAction(componentProperties));
postJavacCreation(componentProperties);
return javacTask;
}
它的方法注释:
Creates the task for creating *.class files using javac. These tasks are created regardless of whether Jack is used or not, but assemble will not depend on them if it is. They are always used when running unit tests.
很明显,就是为了创建 .class 文件。
这一步中,最重要的一步就是注册了一个名叫 JavaCompile 的任务,也就是将 Java 文件和 Java 注解转变成 .class 的 Task。
JavaCompile 的 Task 的代码比较绕,直接跟大家说结果了,最终是调用 JDK 下面的 JavaCompiler 类,动态将 .java 转化成 .class 文件。
当然,不仅仅只有 .class 文件,还有其他的诸如 .kt 和 .jar 等,都需要特定的 Task,才能转化成我们需要的输入源。
第三步 建立原始的输入流
回到第一步,进入 addJavacClassesStream 方法:
protected void addJavacClassesStream(@NonNull ComponentPropertiesImpl componentProperties) {
// create separate streams for the output of JAVAC and for the pre/post javac
// bytecode hooks
TransformManager transformManager = componentProperties.getTransformManager();
boolean needsJavaResStreams =
componentProperties.getVariantScope().getNeedsJavaResStreams();
transformManager.addStream(
OriginalStream.builder(project, "javac-output")
// Need both classes and resources because some annotation
// processors generate resources
.addContentTypes(
needsJavaResStreams
? TransformManager.CONTENT_JARS
: ImmutableSet.of(DefaultContentType.CLASSES))
.addScope(Scope.PROJECT)
.setFileCollection(project.getLayout().files(javaOutputs))
.build());
BaseVariantData variantData = componentProperties.getVariantData();
transformManager.addStream(
OriginalStream.builder(project, "pre-javac-generated-bytecode")
.addContentTypes(
needsJavaResStreams
? TransformManager.CONTENT_JARS
: ImmutableSet.of(DefaultContentType.CLASSES))
.addScope(Scope.PROJECT)
.setFileCollection(variantData.getAllPreJavacGeneratedBytecode())
.build());
transformManager.addStream(
OriginalStream.builder(project, "post-javac-generated-bytecode")
.addContentTypes(
needsJavaResStreams
? TransformManager.CONTENT_JARS
: ImmutableSet.of(DefaultContentType.CLASSES))
.addScope(Scope.PROJECT)
.setFileCollection(variantData.getAllPostJavacGeneratedBytecode())
.build());
}
这个 transformManager 就是处理 Transform 的,它在建立第一个 Transform 的原始数据流。
细心的同学可能发现了,第一个数据流的 contentType 至少也是 DefaultContentType.CLASSES,scope 是 Scope.PROJECT,自定义过 Transform 的同学肯定知道,这样设置我们自定义的 Transform 能够接收到原始数据流。
第四步 创建编译后的任务
回到第一步中的 createPostCompilationTasks 方法,它用来创建编译后的任务:
public void createPostCompilationTasks(@NonNull ApkCreationConfig creationConfig) {
//...
TransformManager transformManager = componentProperties.getTransformManager();
// ...
// java8脱糖
maybeCreateDesugarTask(
componentProperties,
componentProperties.getMinSdkVersion(),
transformManager,
isTestCoverageEnabled);
BaseExtension extension = componentProperties.getGlobalScope().getExtension();
// Merge Java Resources.
createMergeJavaResTask(componentProperties);
// ----- External Transforms -----
// apply all the external transforms.
List customTransforms = extension.getTransforms();
List> customTransformsDependencies = extension.getTransformsDependencies();
boolean registeredExternalTransform = false;
for (int i = 0, count = customTransforms.size(); i < count; i++) {
Transform transform = customTransforms.get(i);
List