build/android-profile 你用到了吗

1,433 阅读2分钟
原文链接: mp.weixin.qq.com

不知大家是否有留意过根目录下 build/android-profile 这个文件夹,常年累月编译下来估计有个好几百 MB 了吧,这里记录着每次构建的一些任务信息,如果它能辅助你排查编译问题或是学习编译过程那可再好不过了,但如果你几乎没打开过它,那么它的存在既浪费硬盘空间又耗费编译时间,来看看它是怎么生成的,能否完全不生成这个文件夹?

起源来自于 com.android.build.gradle.BasePlugin#apply() ,如下图涉及到了四个部分:

先来看看 4 ,threadRecorder.record 可谓是到处都是,贯穿着整个打包的执行,来看看他的具体实现,在 com.android.builder.profile.ThreadRecorder 里:

block.call() 用到了 java8 的 :: ,finally 内的 write 方法值得注意,往里看:

使用 profileRecordWriter 进行了记录,profileRecordWriter 是由 ProcessProfileWriter.get() 获取的,看看实现:

看到这里,大概比较清楚了,android-profile 里的信息是通过调用 threadRecord.record() ,具体由 processProfileWriter 记录。

再来看看 processProfileWriter 是如初始化的?走到 1 ProfilerInitializer.init() 里的调用链看看:

调用 ProcessProfileWriter.get() 初始化 processProfileWriter:

看完了关于 processProfileWriter 的初始化后,来看看 threadRecord 的初始化,threadRecord 是由 ThreadLocal.get() 获取的:

如果 processProfileWriter 初始化了,就用 RECORDER,否则用 NO_OP_RECORDER:

NO_OP_RECORDER 是实例化的 NoOpRecorder,来看看实现:

和之前 ThreadRecorder 的 record 方法实现有区别,NoOpRecorder 是个空实现,并没有 finally 里的 write 操作。那么思路来了,只要我们能将 BasePlugin 里的 ThreadLocal.get() 返回的是 NoOpRecorder 就好办了,那么也就是是否初始化 processProfileWriter 得返回 false,换言之不要初始化 processProfileWriter。

上面已经分析了 processProfile 是在 1 中 ProfilerInitializer.init() 内部调用了 ProcessProfileWriter.get() 完成的初始化,我们如果要达到目的,就不能走初始化,再来仔细看看 ProfilerInitializer.init() 的实现:

可以看到,如果不想走到下面的 ProcessProfileWriter.get() 的话,需要走进箭头位置的 if 就行,即 recordingBuildListener 需要不等于 null,来看看它是啥?

还是用来记录 task 执行前后的信息的,到这为止,思路已经完全有了,再来总结一下:

只要我们能让 1 中执行 ProfilerInitializer.init() 之前让 recordingBuildListener 不为 null,就会走不到 processProfileWriter 的初始化,这样 threadRecorder 就会是 NoOpRecorder,然后我们的目的就打成了。

那怎样让 recordingBuildListener 不为 null?so easy。recordingBuildListener 是个静态变量,反射让他不为 null 就好了:

咦,有万恶的波浪线,原来是 RecordingBuildListener 的构造函数是包访问权限,所以没法在我的插件里初始化。不要紧,我建个一样的包名的类去继承它:

这样就大功告成了,build/android-profile 绝对不会再出现了。