前言
最近,我们项目在接入微信 Matrix,刚开始接入的时候,还蛮顺利的。到了下午,运行项目,偶现 crash。看了一下报错信息,某些 class 文件在 dex 文件中没有找到,即 ClassNotFoundException 。
clean 了一下,发现好了,就继续开发,跑了几次,发现突然又 crash 了,这时候我第一感觉怀疑是 matrix 导致的。
于是,我把 matrix trace 插件关了之后,本地全量编译,还有增量编译,发现都没有这个问题了,于是我可以确定,这肯定是引入 Matrix 带来的问题。
这时候,我就去 github 上面搜 issue,关键字是 ClassNotFoundException ,发现很多人都遇到这个问题,但是一直没有修复。
这时候怎么办呢?是偶现的,不是必现的。那当然要找出复现路径呢?于是,又折腾了半天多,终于发现了复现路径。在增量编译的情况下,修改某个 library moudle 一行代码,可以稳定复现。
于是,又上去上面搜了一波,关键字是增量编译
果不其然,也有挺多人遇到,而且官方也明确标记为 bug,这时候我是怎么解决的呢?
欲知下事如何,请看下文,哈哈,卖一下关子。
现象
我们回到问题的本身,先描述一下现象,问题描述清楚真的很重要,尤其是在网上想别人请教的时候,你懂的。
异常类型:编译异常& app crash
matrix版本:2.0.1
gradle版本:4.1.0
问题描述:第一次编译正常运行,第二次编译运行,会出现某些 class 找不到,报 ClassNotFoundException,出现问题之后需要 clean 项目,运行项目才正常
堆栈信息:
java.lang.NullPointerException at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) at com.tencent.matrix.trace.MethodCollector$TraceClassAdapter.visit(MethodCollector.java:284) at org.objectweb.asm.ClassReader.accept(ClassReader.java:524) at org.objectweb.asm.ClassReader.accept(ClassReader.java:391) at com.tencent.matrix.trace.MethodCollector$CollectJarTask.run(MethodCollector.java:171) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.NullPointerException at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) at com.tencent.matrix.trace.MethodCollector$TraceClassAdapter.visit(MethodCollector.java:284) at org.objectweb.asm.ClassReader.accept(ClassReader.java:524) at org.objectweb.asm.ClassReader.accept(ClassReader.java:391) at com.tencent.matrix.trace.MethodCollector$CollectJarTask.run(MethodCollector.java:171) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.NullPointerException at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) at com.tencent.matrix.trace.MethodCollector$TraceClassAdapter.visit(MethodCollector.java:284) at org.objectweb.asm.ClassReader.accept(ClassReader.java:524) at org.objectweb.asm.ClassReader.accept(ClassReader.java:391) at com.tencent.matrix.trace.MethodCollector$CollectJarTask.run(MethodCollector.java:171) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) [I][MethodCollector] [saveIgnoreCollectedMethod] size:9626 path:D:\githubRep\gradleLearing\app\build\outputs\mapping\debug\ignoreMethodMapping.txt [I][MethodCollector] [saveCollectedMethod] size:24989 incrementCount:24988 path:D:\githubRep\gradleLearing\app\build\outputs\mapping\debug\methodMapping.txt [E][Matrix.MethodTracer] [innerTraceMethodFromJar] input:C:\Users\N21616\.gradle\caches\transforms-2\files-2.1\48590e038f1555cf787fe85359f8a35d\jetified-kotlin-stdlib-jdk7-1.5.20.jar output:D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\36.jar e:java.lang.UnsupportedOperationException: This feature requires ASM6 java.nio.file.FileSystemException: D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\36.jar: 另一个程序正在使用此文件,进程无法访问。 at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) at sun.nio.fs.WindowsFileCopy.copy(WindowsFileCopy.java:165) at sun.nio.fs.WindowsFileSystemProvider.copy(WindowsFileSystemProvider.java:278) at java.nio.file.Files.copy(Files.java:1274) at com.tencent.matrix.trace.MethodTracer.innerTraceMethodFromJar(MethodTracer.java:204) at com.tencent.matrix.trace.MethodTracer.access$100(MethodTracer.java:60) at com.tencent.matrix.trace.MethodTracer$2.run(MethodTracer.java:108) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) > Task :app:transformClassesWithMatrixTraceTransformForDebug [I][Matrix.Trace] [doTransform] Step(1)[Parse]... cost:48ms [I][Matrix.Trace] [doTransform] Step(2)[Collection]... cost:1264ms [E][Matrix.MethodTracer] [innerTraceMethodFromJar] input:C:\Users\N21616\.gradle\caches\transforms-2\files-2.1\bb37a7de696e1bea72b3b0dd87cdc726\jetified-kotlin-stdlib-jdk8-1.5.20.jar output:D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\35.jar e:java.lang.UnsupportedOperationException: This feature requires ASM6 java.nio.file.FileSystemException: D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\35.jar: 另一个程序正在使用此文件,进程无法访问。 at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) at sun.nio.fs.WindowsFileCopy.copy(WindowsFileCopy.java:165) at sun.nio.fs.WindowsFileSystemProvider.copy(WindowsFileSystemProvider.java:278) at java.nio.file.Files.copy(Files.java:1274) at com.tencent.matrix.trace.MethodTracer.innerTraceMethodFromJar(MethodTracer.java:204) at com.tencent.matrix.trace.MethodTracer.access$100(MethodTracer.java:60) at com.tencent.matrix.trace.MethodTracer$2.run(MethodTracer.java:108) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) [E][Matrix.MethodTracer] [innerTraceMethodFromJar] input:C:\Users\N21616\.gradle\caches\transforms-2\files-2.1\32898900927cbb3ddb95f2fe14af33ec\jetified-kotlin-stdlib-1.5.20.jar output:D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\37.jar e:java.lang.UnsupportedOperationException: This feature requires ASM6 java.nio.file.FileSystemException: D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\37.jar: 另一个程序正在使用此文件,进程无法访问。 at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) at sun.nio.fs.WindowsFileCopy.copy(WindowsFileCopy.java:165) at sun.nio.fs.WindowsFileSystemProvider.copy(WindowsFileSystemProvider.java:278) at java.nio.file.Files.copy(Files.java:1274) at com.tencent.matrix.trace.MethodTracer.innerTraceMethodFromJar(MethodTracer.java:204) at com.tencent.matrix.trace.MethodTracer.access$100(MethodTracer.java:60) at com.tencent.matrix.trace.MethodTracer$2.run(MethodTracer.java:108) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) [E][Matrix.MethodTracer] [innerTraceMethodFromJar] input:D:\githubRep\gradleLearing\mylibrary\build\intermediates\runtime_library_classes_jar\debug\classes.jar output:D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\60.jar e:java.util.zip.ZipException: zip file is empty java.util.zip.ZipException: zip file is empty at java.util.zip.ZipFile.open(Native Method) at java.util.zip.ZipFile.<init>(ZipFile.java:225) at java.util.zip.ZipFile.<init>(ZipFile.java:155) at java.util.zip.ZipFile.<init>(ZipFile.java:169) at com.tencent.matrix.trace.MethodTracer.innerTraceMethodFromJar(MethodTracer.java:186) at com.tencent.matrix.trace.MethodTracer.access$100(MethodTracer.java:61) at com.tencent.matrix.trace.MethodTracer$2.run(MethodTracer.java:113) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) [E][Matrix.MethodTracer] [innerTraceMethodFromJar] input:D:\githubRep\gradleLearing\mylibrary\build\intermediates\runtime_library_classes_jar\debug\classes.jar is empty [E][Matrix.MethodTracer] Close stream err! [E][Matrix.MethodTracer] [innerTraceMethodFromJar] input:C:\Users\N21616\.gradle\caches\transforms-2\files-2.1\e378b9fe89a5fe15cf3fa9c9da712ef7\jetified-kotlin-stdlib-jdk8-1.5.20.jar output:D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\35.jar e:java.lang.UnsupportedOperationException: This feature requires ASM6 [E][Matrix.MethodTracer] Close stream err! [E][Matrix.MethodTracer] [innerTraceMethodFromJar] input:C:\Users\N21616\.gradle\caches\transforms-2\files-2.1\ca30333b1699ed3075710b30785c2fac\jetified-kotlin-stdlib-1.5.20.jar output:D:\githubRep\gradleLearing\app\build\intermediates\transforms\MatrixTraceTransform\debug\37.jar e:java.lang.UnsupportedOperationException: This feature requires ASM6 [E][Matrix.MethodTracer] Close stream err! > Task :app:transformClassesWithMatrixTraceTransformForDebug [I][Matrix.Trace] [doTransform] Step(3)[Trace]... cost:2304ms [I][Matrix.TraceTransform] Insert matrix trace instrumentations cost time: 3671ms.
问题直接原因
就像文章开头说的,在本地搞了半天多, 才终于发现必现路径,增编编译,运行的时候,会直接 crash。
于是,我先去官方 issue 上面搜索,一搜,发现很多人都遇到,但是一直没有解决,官方标记为 bug,issue 链接 issue 592, 这里特别感谢他们提供的思路。
可以看到,很多人出现都是增编编译的时候出现问题,
于是,我在想,我先把增量编译关了,看行不行。
说干就干,于是我把 MatrixTraceTransform#isIncremental,MatrixTraceLegacyTransform##isIncremental 都返回 false,发现我们项目增量编译也 ok 了,不会 crash 了。
特意去看了一下编译耗时,在我们项目中,编译一次,transformClassesWithRealmTransformerForDebug,耗时大概是 20 - 30 ms 左右,增量编译在 10 - 15 ms,关闭 matrix transfrom 增量编译的话,大概慢 10 - 15 ms,貌似也可以接受。
菜逼的我留下了眼泪。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4nPvoEz-1637060739950)(https://raw.githubusercontent.com/gdutxiaoxu/blog_image/master/21/08/20211104173950.png)]
问题探索
于是,我先去接入 matrix 相关功能了,但是这个增量编译的问题,一直在想着,到底是什么问题了?有时候吃饭都在想。
想着想着,我再次进入这个坑。gradlew installDebug --stacktrace ,查看编译 error 级别的信息,主要有四个地方,也是我重点怀疑的。
- java.lang.NullPointerException 空指针问题
- ASM 版本的问题,java.lang.UnsupportedOperationException: This feature requires ASM6
- windows 文件 fd 占用问题,对应的提醒信息是 另一个程序正在使用此文件,进程无法访问。
- zip file is empty 问题
第一次尝试,java.lang.NullPointerException 空指针问题?
看堆栈信息,很快定位到 com.tencent.matrix.trace.MethodCollector.TraceClassAdapter#visit,里面有这样一个逻辑
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); this.className = name; if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) { this.isABSClass = true; } collectedClassExtendMap.put(className, superName); }
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 到底是什么东东?搜了一下,发现 module-info.class 不是标准的 class。
module kotlin.stdlib.jdk8 { requires transitive kotlin.stdlib; requires kotlin.stdlib.jdk7; exports kotlin.collections.jdk8; exports kotlin.streams.jdk8; exports kotlin.text.jdk8; opens kotlin.internal.jdk8 to kotlin.stdlib; }
简单来讲,就是JDK9支持模块化,类似Dart语言的包组织,JS的export,这样可以管理或者重新组织一个新的包,而不是像JDK8以下一样,只能通过Java修饰符来控制访问权限;而这个module-info.class就是来管理和描述这个包的;
在JDK8及以下,module-info.class并不会起作用,只有在JDK9以上才会起作用;
可以看到这个class并不是一个正常的class,并不包含类或者方法,所以asm和javassist处理这个class时,就会解析报错;
具体的可以看一下这篇文章
Android Gradle Plugin处理module-info.class报错
第二次尝试,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 流,代码如下
private void innerTraceMethodFromJar(File input, File output) { ZipOutputStream zipOutputStream = null; ZipFile zipFile = null; try { // 省略若干代码 } catch (Exception e) { try { if (zipOutputStream != null) { zipOutputStream.finish(); zipOutputStream.flush(); zipOutputStream.close(); zipOutputStream = null; } if (zipFile != null) { zipFile.close(); zipFile = null; } } catch (Exception e2) { Log.e(TAG, "close stream err!, e2 is "+ e2); } Log.e(TAG, "[innerTraceMethodFromJar] input:%s output:%s e:%s", input, output, e); if (e instanceof ZipException) { e.printStackTrace(); } try { if (input.length() > 0) { Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING); } else { Log.e(TAG, "[innerTraceMethodFromJar] input:%s is empty", input); } } catch (Exception e1) { e1.printStackTrace(); } } finally { try { if (zipOutputStream != null) { zipOutputStream.finish(); zipOutputStream.flush(); zipOutputStream.close(); } if (zipFile != null) { zipFile.close(); } } catch (Exception e) { Log.e(TAG, "close stream err!"); } } }
重新运行,项目跑起来,启动 App,还是一如既往得出人意料, App 直接 crash, 我的天。
你以为我要放弃了嘛,不不,起来,我还能再战个十万回合。