This article launches my wechat official account: Xu Gong

preface

Recently, our project was connected to wechat Matrix. At the beginning, it was quite smooth. In the afternoon, running the project, occasionally crash. Looking at the error message, some class files were not found in the dex file, ClassNotFoundException.

I cleaned it and found that it was fine, so I continued the development. After running for several times, I found that it suddenly crashed again. At that time, I first suspected that it was caused by matrix.

Therefore, after I closed the Matrix Trace plug-in, local full compilation, and incremental compilation, I found that there was no such problem, so I can confirm that this must be a problem caused by the introduction of matrix.

At this point, I went to Github to search for issue with the keyword ClassNotFoundException and found that many people were experiencing this problem but had not fixed it.

What to do? It is accidental, not necessary. So of course you have to find a recurrence path, right? So, and toss about for a long time, finally found the path of reappearance. In the case of incremental compilation, modifying a line of code in a Library moudle can stably reproduce. So, went up again above search a wave, the key word is incremental compilation

Sure enough, there are a lot of people encountered, and the official also clearly marked as a bug, this time I how to solve it?

To see what’s going on, see below.

The phenomenon of

Let’s go back to the problem, and describe the phenomenon, because it’s really important to be clear about the problem, especially when you’re asking people online, you know.

Exception type: Compile exception & APP Crash Matrix version: 2.0.1 Gradle version: 4.1.0 The first compilation is running properly, and the second compilation is running properly. Some classes cannot be found, and ClassNotFoundException is reported

Stack information:

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.
Copy the code

Direct cause of the problem

Just as the article said at the beginning of the article, it took more than a long time to find the required path, add-compile, run, will crash directly.

Therefore, I searched on the official issue first, and found that many people had encountered it, but had not solved it. The official mark is bug, and the link of issue is Issue 592. Here I would like to thank them for their ideas.

As you can see, a lot of people are having problems with add-on compilations, so I’m thinking, LET’s just turn off delta compilations and see if that works.

To start, so I put MatrixTraceTransform# isIncremental, MatrixTraceLegacyTransform# # isIncremental returns false, found out that our project incremental compilation is also ok, It won’t crash.

Go to look at the compile time consuming, in our project, compile time, transformClassesWithRealmTransformerForDebug, would be about 20-30 ms time-consuming, incremental compilation in 10 to 15 ms, With matrix Transfrom incremental compilation turned off, about 10-15 ms slower seems acceptable.

The food made me cry.

To explore problems

Therefore, I first went to access the matrix related functions, but this incremental compilation problem, I have been thinking, what is the problem? Sometimes I think about eating.

With that in mind, I entered the pit again. Gradlew installDebug — StackTrace looks at compile error level information. There are four main things that I suspect.

  • Java. Lang. NullPointerException null pointer
  • Problems with the ASM version,java.lang.UnsupportedOperationException: This feature requires ASM6
  • Windows file fd occupation problem, the corresponding warning message is that another program is using this file and the process cannot access it.
  • zip file is emptyThe problem

First try, Java. Lang. NullPointerException null pointer problem?

Look at the stack information, quickly positioning to com. Tencent. Matrix. Trace. MethodCollector. TraceClassAdapter# visit, there is such a logic

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);
        }
Copy the code

Debug Detects that an error occurs when the className is meta-INF /versions/9/module-info.class and the superName is null. ConcurrentHashMap does not allow the key or value to be null.

So I added nullation logic, code running, App crash. This cause is preliminarily ruled out.

Module-info.class the superName of this Object is null, which is strange because it is not supposed to be null because Java inherits Object by default.

What is this module-info.class? Module-info.class is not a standard 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;
}
Copy the code

In short, JDK9 supports modularization, a package organization similar to the Dart language, and JS export, so that you can manage or reorganize a new package, rather than controlling access through Java modifiers like JDK8. The module-info.class manages and describes this package;

Module-info.class does not work in JDK8 or below, only in JDK9 or above; As you can see, this class is not a normal class and contains no classes or methods, so asm and Javassist will parse the class with an error.

Take a look at this article for details

Android Gradle Plugin handling module-info.class error

Second attempt, ASM version problem?

The build log requires ASM6, which requires the ORACLE ASM version. The oracle ASM version requires ASM6. Rule it out. I don’t think that’s the reason.

On the third try,Windows file fd occupation problem?

Look at the stack information through the code, can be seen here is an error com. Tencent. Matrix. Trace. MethodTracer# innerTraceMethodFromJar

In the process of the concrete plug pile is the cause of error exception occurs, then call Files. The copy (input. ToPath (), the output. The toPath (), StandardCopyOption. REPLACE_EXISTING); Sorry, this only works on Windows, Linux, MAC. I just want to say that the MAC really smells good, without the Windows mess.

So I close the IO stream with a catch exception, and the code is as follows

private void innerTraceMethodFromJar(File input, File output) { ZipOutputStream zipOutputStream = null; ZipFile zipFile = null; Try {// omit some code} 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!" ); }}}Copy the code

It runs again, the project runs, launches the App, and as always, unexpectedly, the App crashes. Oh, my God.

You think I’m giving up? No, no, get up. I can fight another 100,000 rounds.

On the fourth try,zip file is empty?

Through the stack information, where an error about com here. Tencent. The matrix. The trace. MethodTracer# innerTraceMethodFromJar, probably means zip file is empty. And here, for convenience, D: githubRep gradleLearing myLibrary build intermediates runtime_library_classes_jar debug classes.jar classes.jar

For those of you who know about transfrom, we know that transfrom input depends on the output of the previous transfrom.

So, I looked at the Transform Task of our project and found that there were other transfrom. Could that be the reason? (It looks like it’s possible.)

So, I created a new Demo, made sure there was only transfrom for matrix, incremental compilation, and started…

Unfortunately, the screen is still black. So, at this point, it can be confirmed that there must be a problem with matrix Transfrom. This reinforced my determination to look at the Matrix Trace plugin code.

Look at this, we might be a little confused, right?

After incremental compilation is enabled, the ClassNotFound problem is almost certainly caused by the trace pluginclass.jarSize is 0, then it is likely to be processedclass.jarI made a mistake

MethodTracer#innerTraceMethodFromJar(File input, File output) = innerTraceMethodFromJar(File input, File output

com.tencent.matrix.plugin.trace.MatrixTrace#doTransform methodTracer.trace(dirInputOutMap, JarInputOutMap) / / dirInputOutMap here on the com. Tencent. Matrix. Trace. MethodTracer# trace com.tencent.matrix.trace.MethodTracer#traceMethodFromJar com.tencent.matrix.trace.MethodTracer#innerTraceMethodFromJar(File input, File output)Copy the code

Here, we’ll focus on methodtracer. trace(dirInputOutMap, jarInputOutMap) in the MatrixTrace#doTransform method, because that’s where the input is passed.

    fun doTransform(classInputs: Collection
       
        , changedFiles: Map
        
         , inputToOutput: Map
         
          , isIncremental: Boolean, traceClassDirectoryOutput: File, legacyReplaceChangedFile: ((File, Map
          
           )
          ,>
         ,>
        ,>
       -> Object)? , legacyReplaceFile:((File, File) -> (Object))?{

        // Omit some code

        /** * step 1 */
        var start = System.currentTimeMillis()

        val futures = LinkedList<Future><*>>()

        val mappingCollector = MappingCollector()
        val methodId = AtomicInteger(0)
        val collectedMethodMap = ConcurrentHashMap<String, TraceMethod>()

        futures.add(executor.submit(ParseMappingTask(
                mappingCollector, collectedMethodMap, methodId, config)))

        // dirInputOutMap is initialized here
        val dirInputOutMap = ConcurrentHashMap<File, File>()
        val jarInputOutMap = ConcurrentHashMap<File, File>()

        for (file in classInputs) {
            if (file.isDirectory) {
                futures.add(executor.submit(CollectDirectoryInputTask(
                        directoryInput = file,
                        mapOfChangedFiles = changedFiles,
                        mapOfInputToOutput = inputToOutput,
                        isIncremental = isIncremental,
                        traceClassDirectoryOutput = traceClassDirectoryOutput,
                        legacyReplaceChangedFile = legacyReplaceChangedFile,
                        legacyReplaceFile = legacyReplaceFile,

                        In the first place, it is possible to change the value of dirInputOutMap
                        resultOfDirInputToOut = dirInputOutMap
                )))
            } else {
                val status = Status.CHANGED
                futures.add(executor.submit(CollectJarInputTask(
                        inputJar = file,
                        inputJarStatus = status,
                        inputToOutput = inputToOutput,
                        isIncremental = isIncremental,
                        traceClassFileOutput = traceClassDirectoryOutput,
                        legacyReplaceFile = legacyReplaceFile,

                        In the second place, it is possible to change the value of dirInputOutMap
                        resultOfDirInputToOut = dirInputOutMap,
                        resultOfJarInputToOut = jarInputOutMap
                )))
            }
        }

        for (future in futures) {
            future.get()
        }
        futures.clear()

        Log.i(TAG, "[doTransform] Step(1)[Parse]... cost:%sms", System.currentTimeMillis() - start)

        /** * step 2 */
        start = System.currentTimeMillis()
        val methodCollector = MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap)

        methodCollector.collect(dirInputOutMap.keys, jarInputOutMap.keys)
        Log.i(TAG, "[doTransform] Step(2)[Collection]... cost:%sms", System.currentTimeMillis() - start)

        /** * step 3 */
        start = System.currentTimeMillis()
        val methodTracer = MethodTracer(executor, mappingCollector, config, methodCollector.collectedMethodMap, methodCollector.collectedClassExtendMap)
        // Third, it is possible to change the value of dirInputOutMap
        methodTracer.trace(dirInputOutMap, jarInputOutMap)
        Log.i(TAG, "[doTransform] Step(3)[Trace]... cost:%sms", System.currentTimeMillis() - start)

    }
Copy the code

Focus on the possible changes to the dirInputOutMap. The code above has been marked out, and you can see that there are three main possible changes.

So, I added a breakpoint, the breakpoint is in step1, step2, step3 comment place, debug

  • Step1 whenclasses.jarThe size is not zero
  • Step2 whenclasses.jarThe size is not zero
  • Step3 whenclasses.jarThe size is not zero

Some of you might be wondering, D: githubRep gradleLearing myLibrary build intermediates runtime_library_classes_jar debug classes.jar Because the stack we’re reporting is this class.jar with size 0.

Since none of these places is zero, it’s likely that it was changed in the methodTracer.trace(dirInputOutMap, jarInputOutMap) method.

public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) throws ExecutionException, InterruptedException { List<Future> futures = new LinkedList<>(); traceMethodFromSrc(srcFolderList, futures); traceMethodFromJar(dependencyJarList, futures); for (Future future : futures) { future.get(); } futures.clear(); } private void traceMethodFromSrc(Map<File, File> srcMap, List<Future> futures) { if (null ! = srcMap) { for (Map.Entry<File, File> entry : srcMap.entrySet()) { futures.add(executor.submit(new Runnable() { @Override public void run() { innerTraceMethodFromSrc(entry.getKey(), entry.getValue()); }})); }}}Copy the code

The trace method performs two main pieces of logic

  • Perform traceMethodFromSrc
  • Execute the traceMethodFromJar method

And our dirInputOutMap parameter corresponds to the srcFolderList parameter of the trace method, so we set conditional breakpoints at the beginning and end of the innerTraceMethodFromSrc method, On the condition that input.path.equals(“D:\\githubRep\\gradleLearing\\mylibrary\\build\\intermediates\\runtime_library_classes_jar\\debug\\cl asses.jar”)

Debug finds that when the innerTraceMethodFromSrc method is first called (which is important and will be covered later), our classes.jar file size is not zero and can wait until the method is finished executing. The classes.jar file has a size of 0. At this point, you’re almost sure that the innerTraceMethodFromSrc method modified classes.jar to be 0.

InnerTraceMethodFromSrc method, you can see that there are two places to manipulate the file

  • FileUtil.copyFileUsingStream(classFile, changedFileOutput)
  • Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING)
private void innerTraceMethodFromSrc(File input, File output) { ArrayList<File> classFileList = new ArrayList<>(); if (input.isDirectory()) { listClassFiles(classFileList, input); } else { classFileList.add(input); } for (File classFile : classFileList) { InputStream is = null; FileOutputStream os = null; try { final String changedFileInputFullPath = classFile.getAbsolutePath(); final File changedFileOutput = new File(changedFileInputFullPath.replace(input.getAbsolutePath(), output.getAbsolutePath())); if (! changedFileOutput.exists()) { changedFileOutput.getParentFile().mkdirs(); } changedFileOutput.createNewFile(); if (MethodCollector.isNeedTraceFile(classFile.getName())) { is = new FileInputStream(classFile); ClassReader classReader = new ClassReader(is); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter); classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES); is.close(); if (output.isDirectory()) { os = new FileOutputStream(changedFileOutput); } else { os = new FileOutputStream(output); } os.write(classWriter.toByteArray()); os.close(); } else {// When classFile and changedFileOutput are in the same path, Cause ` classes. The jar ` 0 FileUtil. CopyFileUsingStream (classFile changedFileOutput); } } catch (Exception e) { Log.e(TAG, "[innerTraceMethodFromSrc] input:%s e:%s", input.getName(), e); Try {/ / here to manipulate Files. The file copy (input) toPath (), the output. The toPath (), StandardCopyOption. REPLACE_EXISTING); } catch (Exception e1) { e1.printStackTrace(); } } finally { try { is.close(); os.close(); } catch (Exception e) { // ignore } } } }Copy the code

Conditional breakpoints, discovery is FileUtil copyFileUsingStream to copy, because reading and writing a file at the same time, lead to classes. The jar are changed, the content was erased. DirInputOutMap input and Output file paths are the same, so the contents of dirInputOutMap input and output file paths are the same, so the contents of dirInputOutMap input and output file paths are the same.

Little lucky, finally found a solution

DirInputOutMap input and Output file paths are the same.

In the most intuitive way, we try to add the condition that no copy is done when classFile and changedFileOutput paths are the same.

if (! classFile.getAbsolutePath().equals(changedFileOutput.getAbsolutePath())) { FileUtil.copyFileUsingStream(classFile, changedFileOutput } else { Log.e(TAG, "error, name should not be equal, classFile.getAbsolutePath() is "+ classFile.getAbsolutePath()); }Copy the code

Compile the native matrix Trace plugin version, run the demo, and run. You will find that the App works properly and will not crash. I thought that would be the end of it

But this creates a new problem. Without copy when compiling incrementally, our code changes will never take effect. So again, why is the path of the input and output files in dirInputOutMap the same

Remember the MatrixTrace#doTransform method, let’s look at the code executed between step1 and step2

for (file in classInputs) { if (file.isDirectory) { futures.add(executor.submit(CollectDirectoryInputTask( directoryInput = file, mapOfChangedFiles = changedFiles, mapOfInputToOutput = inputToOutput, isIncremental = isIncremental, traceClassDirectoryOutput = traceClassDirectoryOutput, LegacyReplaceChangedFile = legacyReplaceChangedFile, legacyReplaceFile = legacyReplaceFile, / / in the first place, Possible to change dirInputOutMap resultOfDirInputToOut = dirInputOutMap)))} else {val status = status.changed futures.add(executor.submit(CollectJarInputTask( inputJar = file, inputJarStatus = status, inputToOutput = inputToOutput, isIncremental = isIncremental, traceClassFileOutput = traceClassDirectoryOutput, Replacefile = legacyReplaceFile, Possible change dirInputOutMap resultOfDirInputToOut = dirInputOutMap, resultOfJarInputToOut = jarInputOutMap ))) } } for (future in futures) { future.get() } futures.clear()Copy the code

As you can see, this method does two main things

  • Traverse the file, if isDirectory to true, CollectDirectoryInputTask missions
  • If it is a file, execute the CollectJarInputTask task

Let’s take a look at CollectDirectoryInputTask classes, because we mainly focus on dirInputOutMap, we find the usage, Found dirInputOutMap in com. Tencent. Matrix. The plugin. Trace. MatrixTrace. CollectDirectoryInputTask# handle changes

Because incremental compilation is the problem, we set a breakpoint when isIncremental is true, The breakpoint condition is changedFileInput.absolutePath.equals(“D:\\githubRep\\gradleLearing\\mylibrary\\build\\intermediates\\runtime_library_cla sses_jar\\debug\\classes.jar”)

Soon we realize that the paths of changedFileInput and changedFileOutput are exactly the same, If resultOfDirInputToOut[changedFileInput] = changedFileOutput, the resultOfDirInputToOut key and value are the same, this may be the reason.

So, I modified the code, the val changedFileOutput = File (changedFileInputFullPath. Replace (inputFullPath outputFullPath)) plus some judgment.

// mapOfChangedFiles is contains all. each collectDirectoryInputTask should handle itself, should not handle other file if (! changedFileInputFullPath.contains(inputFullPath)) { cCopy the code

Native compilation of matrix Trace plugin, find perfect running, whether full compilation or incremental compilation, perfect. At last the problem was solved.

summary

In fact, I was lucky to find a solution to the problem. Many times, there are some difficult and complicated diseases, investigation for a long time, can not find the root cause. Results are of course the best, if not, in fact, we also have a lot of harvest, in this process we develop the ability to solve problems independently, which is a great help to our own growth.

This time, debugging the Matrix Trace plugin was really confused at the beginning. One day the package is made up, there is a problem, one day there is no problem.

So I tried for a long time locally and finally found the recurrence path. Then I searched the issue and found that many people had encountered this problem but had not solved it yet.

We turned off incremental compilation of the Trace plugin and found that it was OK. But this is a dodge, not a solution. At that time, still quite busy, looked for a day or so, did not find out the reason, a face meng force. I’m just going to add the Matrix function.

However, this problem has been in mind, after three or four days, almost complete access. I went to the source code for the headline. There is really no shortcut, step by step investigation, at the beginning of the time, always thinking of one step in place, want to eat into fat, to see if you can solve all of a sudden, looked at around dizzy. Then I learned, step by step, step by step debugging, one by one investigation, and finally, fortunately, finally found the reason.

At that moment, I was really happy and full of sense of achievement.

As you can see, my approach to solving the problem is as follows:

Search for similar problems – “Try to reproduce the path -” search for similar problems again – “Minimum version verification is an add-on compilation problem -” Find the key information from the log – “Step by step troubleshoot the error information -” Locate the cause – “Step by step find the solution.

Did you quit school? If it were you, how would you solve it? There is a better plan, welcome to comment.

The pull request address

Recommended reading

My 5 years of Android learning road, those years together stepped on the pit

RxJava2 stack information display incomplete solution

Original is not easy, if you feel helpful, you can pay attention to my public numberXu GongRemember to “like” bookmark