fix incremental build - zip file is empty #661
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
异常类型:编译异常& app crash
matrix版本:2.0.1
gradle版本:4.0.0
问题描述:第一次编译正常运行,第二次编译正常,但是运行的时候会 crash,报 dex 文件没有这个 class 文件,每次需要 clean 项目后才能编译成功
堆栈信息:
问题直接原因
于是,我先去官方 issue 上面搜索,一搜,发现很多人都遇到,但是一直没有解决,官方标记为 bug,issue 链接 issue 592, 这里特别感谢他们提供的思路。
可以看到,很多人出现都是增编编译的时候出现问题,
于是,我在想,我先把增量编译关了,看行不行。
说干就干,于是我把
MatrixTraceTransform#isIncremental
,MatrixTraceLegacyTransform##isIncremental
都返回 false,发现我们项目增量编译也 ok 了,不会 crash 了。特意去看了一下编译耗时,在我们项目中,编译一次,transformClassesWithRealmTransformerForDebug
,耗时大概是 20 - 30 ms 左右,增量编译在 10 - 15 ms,关闭 matrix transfrom 增量编译的话,大概慢 10 - 15 ms,貌似也可以接受。问题探索
于是,我先去接入 matrix 相关功能了,但是这个增量编译的问题,一直在想着,到底是什么问题了?有时候吃饭都在想。
想着想着,我再次进入这个坑。再次去看编译信息,怀疑有四个地方
java.lang.UnsupportedOperationException: This feature requires ASM6
windows 文件 fd 占用问题
,对应的提醒信息是 另一个程序正在使用此文件,进程无法访问。zip file is empty
问题java.lang.NullPointerException 空指针问题
看堆栈信息,很快定位到
com.tencent.matrix.trace.MethodCollector.TraceClassAdapter#visit
,里面有这样一个逻辑debug 发现当 className 是
META-INF/versions/9/module-info.class
,superName 为 null,导致报错。因为 ConcurrentHashMap 是不允许 key 或者 value 为 null 的。于是我增加了判空逻辑,代码运行,App crash。初步排除这个原因。
module-info.class 这个 的 superName 为 null,这个很奇怪,按理来说,是不可能为 null 的,因为 java 默认都会继承 Object 。于是我去反编译一下,发现这个 module-info.class 位于
jetified-kotlin-stdlib-jdk7-1.5.20.jar 里面,具体为什么为 null,还没有去看
ASM 版本问题
一开始,编译日志提醒说 requires ASM6,以为是 asm 版本的问题,本地更新了 asm 版本,结果还是会出现 crash。排除,应该不是这个原因。
windows 文件 fd 占用问题
看堆栈信息,通过代码,可看到是在这里报错
com.tencent.matrix.trace.MethodTracer#innerTraceMethodFromJar
具体报错的原因是插桩的过程中发生 exception,这时候调用
Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
出错了,这个只会在 windows 上面出现,linux, mac 都不会。突然想说一句, mac 真香,没有 windows 这些乱七八糟的问题。于是我在 catch exception 的时候,关闭一下 IO 流,代码如下
重新运行,项目跑起来,启动 App,还是一如既往得出人意料, App 直接 crash,MyGold, 我的天。
zip file is empty
通过堆栈信息,报错的地方大概在这里
com.tencent.matrix.trace.MethodTracer#innerTraceMethodFromJar
,大概的意思就是 zip file is empty。这里为了方便,下文统一把
D:\githubRep\gradleLearing\mylibrary\build\intermediates\runtime_library_classes_jar\debug\classes.jar
简称为classes.jar
而我们知道 transfrom input 是依赖于上一个 transfrom 的 output 传递过来的,那有没有可能是上一个 transform 传递过来的时候出错。
于是,我去看了我们项目的 transform task,发现还真的存在其他 transfrom,那有没有可能是这个原因呢?(貌似有这个可能呢)
于是,我新建了一个 Demo,确保只有 matrix 的 transfrom,增量编译,启动。。。。。
可惜,还是黑屏,那么,到这里,可以确定的是,一定是 matrix transfrom 的问题。这再次加强了我去看 matrix trace plugin 代码的决心。
我们重点怀疑
MethodTracer#innerTraceMethodFromJar(File input, File output)
的 input jar size 为 0 ,梳理它的调用逻辑,如下这里,我们主要关注一下
MatrixTrace#doTransform
方法里面的methodTracer.trace(dirInputOutMap, jarInputOutMap)
,因为 input 就是从这里传递过去的。主要关注可能修改 dirInputOutMap 的地方,上面的代码已经标注出来了,可以看到,主要有三个地方可能修改。
于是,我加上断点,断点的地方分别在 step1, step2 ,step3 注释的地方,debug 了一下
classes.jar
大小不为 0classes.jar
大小不为0classes.jar
大小不为 0这里可能会有人有这样的疑问,为什么是看
D:\githubRep\gradleLearing\mylibrary\build\intermediates\runtime_library_classes_jar\debug\classes.jar
这个文件,因为我们报错的堆栈,是这个 class.jar 大小为 0.既然这三个地方都不为 0,那么很有可能,是在 methodTracer.trace(dirInputOutMap, jarInputOutMap) 方法 中修改了。
trace 方法主要执行了两个逻辑
而我们的 dirInputOutMap 参数对应的 trace 方法的 srcFolderList 参数,于是,我们在 innerTraceMethodFromSrc 方法的开始和结束的地方,设置条件断点,条件是
input.path.equals("D:\\githubRep\\gradleLearing\\mylibrary\\build\\intermediates\\runtime_library_classes_jar\\debug\\classes.jar")
debug 发现,在刚开始调用
innerTraceMethodFromSrc
方法的时候(这个方法很重要,下文还会涉及到),我们的classes.jar
文件大小不为 0,可以等到方法执行完成的时候,classes.jar
文件大小为 0。这时候基本可以确定了是 innerTraceMethodFromSrc 方法修改了
classes.jar
,导致大小为 0.innerTraceMethodFromSrc 方法,可以看到有两个地方操作了文件
FileUtil.copyFileUsingStream(classFile, changedFileOutput)
Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING)
进行条件断点的时候,发现是
FileUtil.copyFileUsingStream
进行 copy 的时候,因为同时读写一个文件,导致classes.jar
被更改,内容被抹除。到此,原因已经找到了,即 dirInputOutMap 中 input 和 output file 文件路径一致,导致内容错误,那要怎么解决?解决方案
前面我们说到 dirInputOutMap 中 input 和 output file 文件路径一致,导致内容错误。
那一个最直观的方式,我们尝试加上这样的条件,当 classFile 和 changedFileOutput 路径一致的时候,不进行 copy。
编译本地 matrix trace plugin 版本,运行 demo,跑起来,你会发现 App 正常了,不会 crash 了。
但是这样会带来一个新的问题,增量编译的时候,不进行 copy,那我们代码的变动,永远不会生效。所以,还是得找为什么 dirInputOutMap 中 input 和 output file 的路径是一样的,合理来说,应该是不一致的。
还记得前面的
MatrixTrace#doTransform
方法嘛,我们来看一下 step1 和 step2 之间执行的代码可以看到,这个方法主要干了两件事情
我们先来看一下 CollectDirectoryInputTask 类,因为我们主要是关注 dirInputOutMap,我们 find usage 一下,发现
dirInputOutMap
在com.tencent.matrix.plugin.trace.MatrixTrace.CollectDirectoryInputTask#handle
更改因为是增量编译出现问题,所以,我们在 isIncremental 为 true 的时候设置断点,断点条件为
changedFileInput.absolutePath.equals("D:\\githubRep\\gradleLearing\\mylibrary\\build\\intermediates\\runtime_library_classes_jar\\debug\\classes.jar")
很快我们发现 changedFileInput 和 changedFileOutput 的路径是是一模一样的,即
resultOfDirInputToOut[changedFileInput] = changedFileOutput
中 resultOfDirInputToOut key 和 value 是一致的,那么很有可能就是这个原因。于是,我对代码进行了修改,将
val changedFileOutput = File(changedFileInputFullPath.replace(inputFullPath, outputFullPath))
修改为如下的代码。本地编译 matrix trace plugin,发现完美运行,不管是全量编译,还是增量编译, perfect。到此问题终于解决了。
至于项目中
val changedFileOutput = File(changedFileInputFullPath.replace(inputFullPath, outputFullPath))
的这行代码,我猜测可能跟 AGP 早期的版本有关吧,可能早期,inputFullPath 的路径一定是包含在 changedFileInputFullPath 里面的,然后就写了这样的代码,后面 AGP 升级,导致增量编译有问题,具体的没验证,猜测而已。小结
这一次,调试 matrix trace plugin 插件,刚开始真的是一脸懵逼。一会编出来的包,有问题,一会没有问题。
于是在本地尝试了好久,终于发现了复现路径,然后到 issue 上面也搜了一下,发现很多人遇到这个问题,但是还没有解决。
于是,就先关了 trace 插件的增量编译,发现 OK 了。但是这只是一个规避方案,不是一个解决方案。那时候,还比较忙,看了一天左右,也没找出原因,一脸懵逼。就先去加入 matrix 功能了。
可是,这个问题却一直在脑海中记着,过了三四天,差不多接入完成了。就硬着头条去看源代码了。真的没有捷径,一步步排查,刚开始的时候,总想着一步到位,想一口吃成胖子,看能不能一下子解决,看着看着就绕晕了。后面我就学乖了,一步步来,一步步调试,逐个排查,最终,运气比较好,终于找到原因了。
那一刻,真的是挺开心的,充满满满的成就感。