首先,看到题目我怀疑我有些标题党了。为了吸引眼球,是不是有些不择手段了些。EMMM...要反省一下。废话不多说,进入正题。先来看看我们常见的注册Transform的方式。
class SystracePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.getExtensions().getByType(AppExtension).registerTransform(new CustomTransform())
}
}
那各位就要问了:除了这个还有什么别的方式可以注册?别急,先来看一段代码,
public static void inject(Project project, def variant) {
...
project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override
public void graphPopulated(TaskExecutionGraph taskGraph) {
for (Task task : taskGraph.getAllTasks()) {
if ((task.name.equalsIgnoreCase(hackTransformTaskName) ||
task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))
&& !(((TransformTask) task).getTransform() instanceof SystemTraceTransform)) {
project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name)
project.logger.info("variant name: " + variant.name)
Field field = TransformTask.class.getDeclaredField("transform")
field.setAccessible(true)
//注意了!!!
field.set(task, new SystemTraceTransform(project, variant, task.transform))
project.logger.warn("transform class after hook: " + task.transform.getClass())
break
}
}
}
})
}
各位又说了,这没头没尾的一段想说明什么?大佬们不要心急,且听我慢慢道来。上面这一段并不是出自我手。这是张绍文在极客时间上开设的专栏《Android开发高手课》中,第七章中的一个简单的课后sample,Github地址。各位可自行下载查验。按照sample的说明运行后发现,这个项目没有文章刚开始提到的那种常见的注册方式,而貌似和Transform注册挂钩的就是我上面摘出的这一段了。
是不是会有这个疑问:这种注册方式同传统的注册方式有什么不同?
专栏也有读者提出了这个疑问,
Q: 请问project.getProperties().get("android").registerTransform跟示例的注册Transform方式哪一种更好点呢?
A: 因为我们是想修改原来transform的逻辑,通过hook的方式可以控制的更好,而且关键还有时序的问题。
EMMM...这个答案确实没有解决了我心中的疑问。那么只好自己动手了。
在上一篇apply plugin: 'xxx'到底做了啥中,我对apply plugin这一过程进行了简单的分析,文中也提到很多细节忽略了。这里我们聚焦该过程中一处细节,看看能不能挖出我们想要的东西。我们从上一篇结束的地方开始,taskManager#createTasksForVariantData中会创建各种任务,其中有一个与本文关系密切,方法如下,
public void createPostCompilationTasks(TaskFactory tasks, VariantScope variantScope) {
...
AndroidConfig extension = variantScope.getGlobalScope().getExtension();
List<Transform> customTransforms = extension.getTransforms();
List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();
int i = 0;
AndroidTask dexTask;
for(int count = customTransforms.size(); i < count; ++i) {
Transform transform = (Transform)customTransforms.get(i);
dexTask = transformManager.addTransform(tasks, variantScope, transform);
if (dexTask != null) {
List<Object> deps = (List)customTransformsDependencies.get(i);
if (!deps.isEmpty()) {
dexTask.dependsOn(tasks, new Object[]{deps});
}
if (transform.getScopes().isEmpty()) {
variantData.assembleVariantTask.dependsOn(new Object[]{tasks, dexTask});
}
}
}
...
}
我们将最重要的一条语句单独摘出来,
dexTask = transformManager.addTransform(tasks, variantScope, transform);
这说明从BasePlugin#apply开始,到ApplicationTaskManager#createPostCompilationTasks的逻辑中涉及到transform操作,当然这里还没有涉及到transform内部逻辑的调用,但这里确实已经开始着手准备了。transform内部什么时候执行我们先不管,也不是这里的重点。
这里不禁发问,TaskManager#createPostCompilationTasks中的transform从哪里来?
先来探知一下transform的源头。trasform是从customTransforms集合中遍历而来,customTransforms是调用extension#getTransforms而来,extension是这样获得的
AndroidConfig extension =variantScope.getGlobalScope().getExtension();
variantScope类型为VariantScope接口,实现类为VariantScopeImpl,VariantScopeImpl#getGlobalScope为
public GlobalScope getGlobalScope() {
return this.globalScope;
}
再看GlobalScope#getExtension
public AndroidConfig getExtension() {
return this.extension;
}
extension类型为接口AndroidConfig,看一下AndroidConfig的类继承结构图。
看到Extension类,熟悉的感觉回来了,有没有。我们知道自定义Transform注册过程是调用 BaseExtension#registerTransform
public void registerTransform(Transform transform, Object... dependencies) {
this.transforms.add(transform);
this.transformDependencies.add(Arrays.asList(dependencies));
}
再看一下BaseExtension#getTransforms
public List<Transform> getTransforms() {
return ImmutableList.copyOf(this.transforms);
}
是不是和上面完美衔接。createPostCompilationTasks中extension.getTransforms()返回的就是我们通常注册Transform填充的transforms容器的Copy版本。也就是说按照常规注册Transform,在apply plugin时就会涉及到Transform的准备工作(Transform是否会在该过程中被执行还未查明)。
这一部分内容告一段落,截至目前,本文有点像是上一篇的细节展开。接下来,我们还是回归sample的那段代码,直接聚焦关键代码:
project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override
public void graphPopulated(TaskExecutionGraph taskGraph) {
for (Task task : taskGraph.getAllTasks()) {
if ((task.name.equalsIgnoreCase(hackTransformTaskName) ||
task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))
&& !(((TransformTask) task).getTransform() instanceof SystemTraceTransform)) {
Field field = TransformTask.class.getDeclaredField("transform")
field.setAccessible(true)
field.set(task, new SystemTraceTransform(project, variant, task.transform))
project.logger.warn("transform class after hook: " + task.transform.getClass())
break
}
}
}
})
注意到,for循环所处位置是在TaskExecutionGraphListener的回调方法中,也就是说,for循环是在任务图(TaskGraph)构建完成后才执行的,记住这一点,我们后续会再接着说。
注意到for循环中的if条件判断,先不去管其中的逻辑,我有点想知道hackTransformTaskName和hackTransformTaskNameForWrapper是什么?这里需要我们打印log查看一下,但是请注意,Transform类中的逻辑是发生在构建过程中,想看到输出的log可没有那么容易。而且,一旦修改了Transform类中的逻辑,需要重新发布到库(无论是远程库Maven、Ivy,还是本地库),然后重新构建systrace-samle-android,这样你想要看到的log才会在构建过程日志中出现。
所以,先去看看这个systrace-gradle-plugin发布到哪里了。当然,最好是发布到本地,这样我们可以任意在构建部分的代码添加调试代码,发布也方便。而且作为一个sample,我猜应该是发布在本地。如图2,在项目结构中gradle目录下有一个java-publish.gradle的文件。从文件名就知道该gradle文件与发布有关,可以先点进去看看。
同样摘一部分java-publish.gradle的跟发布有关的内容
apply plugin: 'maven-publish'
publishing {
publications {
Component(MavenPublication) {
from components.java
groupId = group
artifactId = POM_ARTIFACT_ID
version = version
artifact sourcesJar
artifact javadocJar
}
}
}
task buildAndPublishToLocalMaven(type: Copy, dependsOn: ['build', 'publishToMavenLocal']) {
group = 'geektime'
// save artifacts files to artifacts folder
from configurations.archives.allArtifacts.files
into "${rootProject.buildDir}/outputs/artifacts/"
rename { String fileName ->
fileName.replace("release.aar", "${version}.aar")
}
doLast {
println "* published to maven local: ${project.group}:${project.name}:${project.version}"
}
}
文件中有一个buildAndPublishToLocalMaven的任务,可以看到发布路径
"${rootProject.buildDir}/outputs/artifacts/"
这一点在项目的对应路径下可以得到印证。也就是说,Chapter07项目中的自定义插件是直接发布在本地。这样的话我们就可以直接在变量下添加打印log,然后重新发布就可以了。
String hackTransformTaskName = getTransformTaskName(
"",
"",variant.name
)
Log.i(TAG, "hackTransformTaskName:%s"+hackTransformTaskName)
在AndroidStudio右侧Gradle标签栏下找到geektime,双击buildAndPublishToLocalMaven任务运行。
构建成功后,单独加载工程systrace-sample-android。直接build,在build日志中得到变量hackTransformTaskName的值。
这样得知,hackTransformTaskNameForWrapper的值为transformClassesWithDexBuilderFor[Variant],(Variant取值为Debug和Release)。
那么if判断逻辑为:名称为transformClassesWithDexBuilderFor[Variant]或transformClassesWithDexFor[Variant]的任务且该任务的transform字段类型不为SystemTraceTransform。
if ((task.name.equalsIgnoreCase(hackTransformTaskName) ||
task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))&&
!(((TransformTask) task).getTransform() instanceof SystemTraceTransform)) {...}
这里,我也把构建过程中执行的task打印出来,从图6可以看到确实有一个名为transformClassesWithDexBuilderForDebug的任务,当然,图6中输出的是构建类型为debug的task,当然还有一个transformClassesWithDexBuilderForRelease的任务。 if块中的逻辑则是把transformClassesWithDexBuilderForDebug/transformClassesWithDexBuilderForRelease的transform字段替换为SystemTraceTransform类型的对象。貌似这样就完成了注册了,因为方法到此已经结束。所以...
等等!!!注意if判断中最后一个判断条件
!(((TransformTask) task).getTransform() instanceof SystemTraceTransform)
TransformTask又是什么任务?它与Transform有什么关系?
额...
EMMM...
让我回头看看,我是不是遗漏了什么...
好吧,确实有。
TaskManager#createPostCompilationTasks中有一句,
dexTask = transformManager.addTransform(tasks, variantScope, transform);
我们应该看一下TransformManager#addTransform逻辑,如下,我只保留了对本文来说最重要的部分。
public <T extends Transform> AndroidTask<TransformTask> addTransform(TaskFactory taskFactory, BaseScope scope, T transform, ConfigActionCallback<T> callback) {
if(...){
...
} else {
String taskName = scope.getTaskName(getTaskNamePrefix(transform));
...
if (...) {
...
} else {
...
this.transforms.add(transform);
//create transform task
AndroidTask<TransformTask> task = this.taskRegistry.create(taskFactory, new ConfigAction(scope.getVariantConfiguration().getFullName(), taskName, transform, inputStreams, referencedStreams, outputStream, callback));
...
return task;
}
}
}
transforms为TransformManager的一个类型为List<Transform>全局容器,将传入方法的transform添加到容器,然后!!!你没看错,TransformManager根据taskName, transform, variantConfiguration等元素创建了TransformTask类型的任务,最后返回了该任务。明白了?Transform最后转为了相应的TransformTask。
所以,理到这里,其中的脉络有些清晰了。那Transform注册到底有几种方式呢?实质上是一种,只不过常规注册是官方提供的标准方式。这里我们总结一下常规流程:apply plugin xxxPluginId会触发createPostCompilationTasks执行;接着TransformManager#addTransform执行,其中添加的transform就是调用registerTransform方法注册的transform;addTransform方法根据transform创建相应的TransformTask。至于Transform的逻辑什么时候执行,本文没有涉及。
而《Android高手课》的sample是一种customized,即定制化的方式。注册发生在TransformTask创建完成且TaskGraph构建好了以后,通过taskName找到TransformTask,利用反射替换TransformTask中的transform字段为项目中自定义的SystemTraceTransform,完成符合条件的字节码文件修改目的。
至于那个朋友问的,哪种方法好?我觉得选择适合自己的就行,如果你对构建过程及Gradle生命周期很熟悉,那就在合适的时机注入Transform。如果还在学习构建及Gradle,那就老老实实的用常规方法注册,毕竟成长为大佬还是需要时间和实践的。
后记
本文是对Gradle Transform部分及《Android高手课》Chapter07的sample的一个浅显片面的解读,或许还没有Get到项目中的真正细节和精髓,如有错误,欢迎指出,板砖轻拍😀。