version

v0.6.5

An overview of the

This article is the first in a series of Matrix series on Tencent’s open source APM framework, and will read the code of matrix-trace-Canary module. We know that the entry to gradle Plugin must be a class that inherits the Plugin, which in matrix-trace-Canary corresponds to the MatrixPlugin, so we’ll start with this class.

1. MatrixPlugin

The MatrixPlugin has only one method, Apply, which does both the creation of an Extension and the registration of a Task

Void the apply (Project Project) {/ / create the extension Project. The extensions. Create ("matrix", MatrixExtension)
        project.matrix.extensions.create("trace", MatrixTraceExtension)
        project.matrix.extensions.create("removeUnusedResources", MatrixDelUnusedResConfiguration)
        ....
        project.afterEvaluate {
          ....
            android.applicationVariants.all { variant ->

                if(the configuration. The trace. The enable) {/ / injection MatrixTraceTransform [2.1] com.tencent.matrix.trace.transform.MatrixTraceTransform.inject(project, configuration.trace, Variantdata ().getScope())} // Remove useless resources available [see 7.1]if (configuration.removeUnusedResources.enable) {
                    if (Util.isNullOrNil(configuration.removeUnusedResources.variant) || variant.name.equalsIgnoreCase(configuration.removeUnusedResources.variant)) {
                        Log.i(TAG, "removeUnusedResources %s", configuration.removeUnusedResources)
                        RemoveUnusedResourcesTask removeUnusedResourcesTask = project.tasks.create("remove" + variant.name.capitalize() + "UnusedResources", RemoveUnusedResourcesTask)
                        removeUnusedResourcesTask.inputs.property(RemoveUnusedResourcesTask.BUILD_VARIANT, variant.name)
                        project.tasks.add(removeUnusedResourcesTask)
                        removeUnusedResourcesTask.dependsOn variant.packageApplication
                        variant.assemble.dependsOn removeUnusedResourcesTask
                    }
                }

            }
        }
    }
Copy the code

2. MatrixTraceTransform

MatrixTraceTransform inherits Transform, which hooks the system to build Dex’s Transform and works with ASM framework to insert bytecode of method execution time. This series of contents will be the focus of this article.

2.1 MatrixTraceTransform. Inject
public static void inject(Project project, MatrixTraceExtension extension, VariantScope variantScope) { ... Configuration config = new configuration.builder ().setPackagename (varie.getApplicationId ())// Package name SetBaseMethodMap (extension. GetBaseMethodMapFile ()) / / build gradle baseMethodMapFile configuration in the preservation of pile method is we need to be specified SetBlackListFile (extension. GetBlackListFile ()) / / build gradle configured in blackListFile, SetMethodMapFilePath (mappingOut +) saves files that do not need to be staked"/methodMapping.txt") / / pile methodId inserted records and the relationship between the method. The setIgnoreMethodMapFilePath (mappingOut +"/ignoreMethodMapping.txt"SetMappingPath (mappingOut) // Mapping file storage directory. SetTraceClassOut (traceClassOut)// After staking class storage directory.build(); Try {// Get TransformTask.. Specific names are as follows: TransformClassesWithDexBuilderForDebug and transformClassesWithDexForDebug / / concrete which should be related to gradle version / / proguard before the task Operation has been completed String [] hardTask = getTransformTaskName (extension. GetCustomDexTransformName (), the variant. The getName ());for (Task task : project.getTasks()) {
                for(String STR: hardTask) {// Find task and hook itif (task.getName().equalsIgnoreCase(str) && task instanceof TransformTask) {
                        TransformTask transformTask = (TransformTask) task;
                        Log.i(TAG, "successfully inject task:" + transformTask.getName());
                        Field field = TransformTask.class.getDeclaredField("transform");
                        field.setAccessible(true); // Set the system"transformClassesWithDexBuilderFor.."and"transformClassesWithDexFor.."[see 2.2] Field. Set (task, new MatrixTraceTransform(config, transformTask.getTransform()));break; } } } } catch (Exception e) { Log.e(TAG, e.toString()); }}Copy the code
2.2 MatrixTraceTransform constructor

MatrixTraceTransform saves the hook Transform, because it needs to execute the MatrixTraceTransform and then restore the original process.

Public MatrixTraceTransform(Configuration config, Transform origTransform) {this.config = config; // The original Transform is the same as the hook Transform this.origTransform = origTransform; }Copy the code

3. MatrixTraceTransform.transform

Above will MatrixTraceTransform. Inject () happened in gradle evaluation period, that is to say, in the evaluation period has confirmed the gradle Task execution order, At runtime gradle calls back to the Transform method of Transform.

@Override public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation); . Try {//doTransform(transformInvocation); } catch (ExecutionException e) { e.printStackTrace(); }... // Origtransform. Transform (transformInvocation); . }Copy the code
3.1 MatrixTraceTransform.doTransform

The functionality of the doTransform method can be divided into three steps

  1. Parse the mapping file to record the mapping between methods before and after the confusion and replace file directories
  2. Collect methods that require and do not require staking and record them in the mapping file and collect inheritance relationships between classes
  3. Bytecode staking is performed
    private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {/ / whether incremental compilation final Boolean isIncremental = transformInvocation. IsIncremental && () this.isIncremental(); /** * step 1 * 1. Parse the mapping file. */ long start = system.currentTimemillis (); List<Future> futures = new LinkedList<>(); Final MappingCollector MappingCollector = new MappingCollector(); // methodId counter Final AtomicInteger methodId = new AtomicInteger(0); TraceMethod final ConcurrentHashMap<String, TraceMethod> collectedMethodMap = new ConcurrentHashMap<>(); Add (Executor. Submit (new ParseMappingTask(mappingCollector, collectedMethodMap, methodId))); Map<File, File> dirInputOutMap = new ConcurrentHashMap<>(); Map<File, File> jarInputOutMap = new ConcurrentHashMap<>(); Collection<TransformInput> inputs = transformInvocation.getInputs();for (TransformInput input : inputs) {

            for(DirectoryInput directoryInput : Input. GetDirectoryInputs ()) {/ / [4.1] futures. The add (executor. Submit (new CollectDirectoryInputTask (dirInputOutMap, directoryInput, isIncremental))); }for(JarInput inputJar : Input. GetJarInputs ()) {//【 see 4.3】 futures. Add (executor. Submit (new CollectJarInputTask(inputJar, isIncremental, jarInputOutMap, dirInputOutMap))); }}for(Future Future: futures) {// Wait for all threads to run future.get(); } // Futures. Clear (); Log.i(TAG,"[doTransform] Step(1)[Parse]... cost:%sms", System.currentTimeMillis() - start); /** * step 2 * 1. Collect methods that require and do not require piling and record them in the Mapping file * 2. Collect inheritance relationships between classes */ start = system.currentTimemillis (); // Collect information about the method needed for piling, MethodCollector MethodCollector = new MethodCollector(Executor, mappingCollector, methodId, MethodCollector) config, collectedMethodMap); Methodcollector.collect (dirinputOutmap.keyset (), jarinputOutmap.keyset ())); Log.i(TAG,"[doTransform] Step(2)[Collection]... cost:%sms", System.currentTimeMillis() - start); /** * start = system.currentTimemillis (); // Execute the pin logic, MethodTracer = new MethodTracer(executor, mappingCollector, config, methodCollector.getCollectedMethodMap(), methodCollector.getCollectedClassExtendMap()); // [see 6.1] methodTracer.trace(dirInputOutMap, jarInputOutMap); Log.i(TAG,"[doTransform] Step(3)[Trace]... cost:%sms", System.currentTimeMillis() - start);

    }
Copy the code

4. CollectDirectoryInputTask

4.1 CollectDirectoryInputTask. Run
public void run() {try {//【 see 4.2】 handle(); } catch (Exception e) { e.printStackTrace(); Log.e("Matrix." + getName(), "%s", e.toString()); }}Copy the code
4.2 CollectDirectoryInputTask. Handle

This method modifies the properties of the input file through reflection. In incremental compilation mode, the file and changedFiles properties are modified, and in full compilation mode, the file property is modified

private void handle() throws IOException, IllegalAccessException, NoSuchFieldException, Final File dirInput = directoryInput.getFile(); ClassNotFoundException {// Obtain the original File final File dirInput = directoryinput.getfile (); Final File dirOutput = new File(traceClassOut, dirInput.getName()); final String inputFullPath = dirInput.getAbsolutePath(); final String outputFullPath = dirOutput.getAbsolutePath(); .if(isIncremental) {/ / incremental updates, only the operating File with the changes in the Map < File, Status > fileStatusMap = directoryInput. GetChangedFiles (); Final map <File, Status> outChangedFiles = new HashMap<>();for(Map.Entry<File, Status> entry : fileStatusMap.entrySet()) { final Status status = entry.getValue(); final File changedFileInput = entry.getKey(); final String changedFileInputFullPath = changedFileInput.getAbsolutePath(); // The previous build output in incremental build mode is redirected to dirOutput; Replace the output directory of the final File changedFileOutput = new File (changedFileInputFullPath. Replace (inputFullPath outputFullPath));if(status = = status. ADDED | | status = = status. The CHANGED) {/ / new, modify the Class files, Dirinputoutmap. put(changedFileInput, changedFileOutput); }else if(status == status.removed) {// Remove Class files directly from changedFileOutput.delete(); } outChangedFiles.put(changedFileOutput, status); } // replaceChangedFile(directoryInput, outChangedFiles) replaces directoryInput with reflection; }else{// In full compilation mode, all Class files need to be scanned dirinputOutmap. put(dirInput, dirOutput); } // reflect input and set dirOutput to its output directory replaceFile(directoryInput, dirOutput); }Copy the code
4.3 CollectJarInputTask. Run

Work and CollectDirectoryInputTask CollectJarInputTask is basically the same, only the operating goals from folder into the jar

        @Override
        public void run() {try {【 see 4.4】 handle(); } catch (Exception e) { e.printStackTrace(); Log.e("Matrix." + getName(), "%s", e.toString()); }}Copy the code
4.4 CollectJarInputTask. Handle
private void handle() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, IOException {// traceClassOut Folder address String traceClassOut = config.traceClassOut; final File jarInput = inputJar.getFile(); Final File jarOutput = new File(traceClassOut, getUniqueJarName(jarInput)); .if (IOUtil.isRealZipOrJar(jarInput)) {
                if(isIncremental) {// isIncrementalif(inputJar getStatus () = = Status. The ADDED | | inputJar. GetStatus () = = Status. The CHANGED) {/ / stored in jarInputOutMap jarInputOutMap.put(jarInput, jarOutput); }else if(inputJar.getStatus() == Status.REMOVED) { jarOutput.delete(); }}elseJarinputoutmap. put(jarInput, jarOutput); }}else{// specifically for handling WeChat autodex. jar files can be skipped, not meaningful.... } // Replace inputJar's file property with jarOutput replaceFile(inputJar, jarOutput); }Copy the code

5. MethodCollector

5.1 MethodCollector. Collect
// Store class -> Parent class map (used to find Activity subclasses) Private final ConcurrentHashMap<String, String> collectedClassExtendMap = new ConcurrentHashMap<>(); Private final ConcurrentHashMap<String, TraceMethod> collectedIgnoreMethodMap = new ConcurrentHashMap<>(); Private final ConcurrentHashMap<String, TraceMethod> collectedMethodMap; private final Configuration configuration; private final AtomicInteger methodId; Private final AtomicInteger ignoreCount = new AtomicInteger(); Private final AtomicInteger incrementCount = new AtomicInteger(); . /** ** @param srcFolderList Original file set * @param dependencyJarList Original JAR set * @throws ExecutionException * @throws InterruptedException */ public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException { List<Future> futures = new LinkedList<>();forArrayList<File> classFileList = new ArrayList<>();if (srcFile.isDirectory()) {
                listClassFiles(classFileList, srcFile);
            } else{ classFileList.add(srcFile); } // This should be a bug, thisforShould I not lie to you? WellforThe outside of thefor(File classFile : ClassFileList) {// Each source file executes CollectSrcTask [see 5.2] futures. Add (executor.submit(new CollectSrcTask(classFile)); }}for(File jarFile : DependencyJarList) {// Execute CollectJarTask for each jar source [see 5.5] futures. Add (executor.submit(new CollectJarTask(jarFile))); }for (Future future : futures) {
            future.get();
        }
        futures.clear();

        futures.add(executor.submit(new Runnable() {
            @Override
            public void run() {/ / store is not needed to pile method of information to a file (including the methods on the blacklist) [5.6] saveIgnoreCollectedMethod (mappingCollector); }})); futures.add(executor.submit(newRunnable() {
            @Override
            public void runSaveCollectedMethod (mappingCollector) {// Save the method information to the file [see 5.7] saveCollectedMethod(mappingCollector); }}));for (Future future : futures) {
            future.get();
        }
        futures.clear();

    }
Copy the code
5.2 CollectSrcTask. Run
        public void run() { InputStream is = null; try { is = new FileInputStream(classFile); ClassReader classReader = new ClassReader(is); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); Visitor = new TraceClassAdapter(opcodes.asm5, classWriter); classReader.accept(visitor, 0); } catch (Exception e) { ... }Copy the code
5.3 TraceClassAdapter. Visit

When it comes to the TraceClassAdapter class, the ASM framework comes into play. ASM calls back the visit and visitMethod methods as it scans the class

private class TraceClassAdapter extends ClassVisitor { .... 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 it is a virtual class or interface isABSClass =true
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true; } / / endures collectedClassExtendMap collectedClassExtendMap. Put (className, superName); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {if(isABSClass) {// Do not care if it is a virtual class or interfacereturn super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                if(! HasWindowFocusMethod) {// Whether the method matches the onWindowFocusChange method signature // (whether the onWindowFocusChange method is overridden in the class, Activity without considering the Class confusion) hasWindowFocusMethod = isWindowFocusChangeMethod (name, desc); } // Perform method collection in CollectMethodNode [see 5.4]returnnew CollectMethodNode(className, access, name, desc, signature, exceptions); }}}Copy the code
5.4 CollectMethodNode

CollectMethodNode inherits MethodNode, and the ASM framework calls back the visitEnd method in MethodNode when scanning methods

private class CollectMethodNode extends MethodNode {
        ....
        @Override
        public void visitEnd() { super.visitEnd(); TraceMethod TraceMethod = TraceMethod. Create (0, access, className, name, desc); // if it is a constructorif ("<init>".equals(name)) {
                isConstructor = true; } Boolean isNeedTrace = isNeedTrace(Configuration, traceMethod. ClassName, mappingCollector); // Ignore empty methods, get/setMethod, a simple method without local variablesif((isEmptyMethod () | | isGetSetMethod () | | isSingleMethod () && isNeedTrace) {/ / ignore methods increasing ignoreCount. IncrementAndGet (); / / added to the map collectedIgnoreMethodMap overlooked method. The put (traceMethod. GetMethodName (), traceMethod);return; } // Methods that are not in the blacklist and are not configured in methodMapping are added to the pile collection;if(isNeedTrace && ! collectedMethodMap.containsKey(traceMethod.getMethodName())) { traceMethod.id = methodId.incrementAndGet(); collectedMethodMap.put(traceMethod.getMethodName(), traceMethod); incrementCount.incrementAndGet(); }else if(! isNeedTrace && ! CollectedIgnoreMethodMap. Either containsKey (traceMethod. ClassName)) {/ / in the blacklist and never in the methodMapping configuration method of adding ignore the set of pile ignoreCount.incrementAndGet(); collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod); }}... }Copy the code
5.5 CollectJarTask. Run

CollectJarTask and CollectSrcTask call the TraceClassAdapter for method scanning

    public void run() {
            ZipFile zipFile = null;

            try {
                zipFile = new ZipFile(fromJar);
                Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
                while (enumeration.hasMoreElements()) {
                    ZipEntry zipEntry = enumeration.nextElement();
                    String zipEntryName = zipEntry.getName();
                    if(isNeedTraceFile(zipEntryName)) {InputStream InputStream = zipfile.getinputStream (zipEntry); ClassReader classReader = new ClassReader(inputStream); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); Visitor = new TraceClassAdapter(opcodes.asm5, classWriter); classReader.accept(visitor, 0); }}}...Copy the code
5.6 MethodCollector.saveIgnoreCollectedMethod

SaveIgnoreCollectedMethod method is very simple, is neglected in front of a mobile phone to method content wrote ignoreMethodMapping. TXT

/** * Store ignored method names in ignoremethodMapping.txt * @param mappingCollector */ private void SaveIgnoreCollectedMethod (MappingCollector MappingCollector) {/ / create ignoreMethodMapping. TXT File object File methodMapFile = new File(configuration.ignoreMethodMapFilePath); // If his father does not existif(! methodMapFile.getParentFile().exists()) { methodMapFile.getParentFile().mkdirs(); } List<TraceMethod> ignoreMethodList = new ArrayList<>(); ignoreMethodList.addAll(collectedIgnoreMethodMap.values()); Log.i(TAG,"[saveIgnoreCollectedMethod] size:%s path:%s", collectedIgnoreMethodMap.size(), methodMapFile.getAbsolutePath()); // Sort collections.sort (ignoreMethodList, new Comparator<TraceMethod>() { @Override public int compare(TraceMethod o1, TraceMethod o2) {returno1.className.compareTo(o2.className); }}); PrintWriter pw = null; try { FileOutputStream fileOutputStream = new FileOutputStream(methodMapFile,false);
            Writer w = new OutputStreamWriter(fileOutputStream, "UTF-8");
            pw = new PrintWriter(w);
            pw.println("ignore methods:");
            forTraceMethod TraceMethod: ignoreMethodList) {// Convert obfuscated data to original data TraceMethod. Revert (mappingCollector); / / output ignore information to file pw. The println (traceMethod. ToIgnoreString ()); } } catch (Exception e) { Log.e(TAG,"write method map Exception:%s", e.getMessage());
            e.printStackTrace();
        } finally {
            if(pw ! = null) { pw.flush(); pw.close(); }}}Copy the code
5.7 MethodCollector.saveCollectedMethod

SaveCollectedMethod is a method that needs to be plugged into methodMapping.txt

@param mappingCollector */ private void saveCollectedMethod(mappingCollector mappingCollector) { File methodMapFile = new File(configuration.methodMapFilePath);if(! methodMapFile.getParentFile().exists()) { methodMapFile.getParentFile().mkdirs(); } List<TraceMethod> methodList = new ArrayList<>(); // Since the Android package will not be inserted, But we need the execution time of the dispatchMessage method // so add this exception to TraceMethod extra = TraceMethod.create(TraceBuildConstants.METHOD_ID_DISPATCH, Opcodes.ACC_PUBLIC,"android.os.Handler"."dispatchMessage"."(Landroid.os.Message;) V");
        collectedMethodMap.put(extra.getMethodName(), extra);

        methodList.addAll(collectedMethodMap.values());

        Log.i(TAG, "[saveCollectedMethod] size:%s incrementCount:%s path:%s", collectedMethodMap.size(), incrementCount.get(), methodMapFile.getAbsolutePath()); Collections.sort(methodList, new Comparator<TraceMethod>() {@override public int compare(TraceMethod o1, TraceMethod o2) {returno1.id - o2.id; }}); PrintWriter pw = null; try { FileOutputStream fileOutputStream = new FileOutputStream(methodMapFile,false);
            Writer w = new OutputStreamWriter(fileOutputStream, "UTF-8");
            pw = new PrintWriter(w);
            for (TraceMethod traceMethod : methodList) {
                traceMethod.revert(mappingCollector);
                pw.println(traceMethod.toString());
            }
        } catch (Exception e) {
            Log.e(TAG, "write method map Exception:%s", e.getMessage());
            e.printStackTrace();
        } finally {
            if(pw ! = null) { pw.flush(); pw.close(); }}}Copy the code

6. MethodTracer.trace

The trace method actually starts the piling

public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) throws ExecutionException, InterruptedException { List<Future> futures = new LinkedList<>(); TraceMethodFromSrc (srcFolderList, futures); < span style = "margin: 0px; margin: 0px; margin: 0px;for (Future future : futures) {
            future.get();
        }
        futures.clear();
    }
Copy the code
6.1 MethodTracer.traceMethodFromSrc
    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() {//【 see 6.2】 innerTraceMethodFromSrc(entry.getkey (), entry.getValue()); }})); }}}Copy the code
6.2 MethodTracer.innerTraceMethodFromSrc
private void innerTraceMethodFromSrc(File input, File output) {
for(File classFile : classFileList) { InputStream is = null; FileOutputStream os = null; Try {/ / the original file path all final String changedFileInputFullPath = classFile. GetAbsolutePath (); / / insert File after pile final File changedFileOutput = new File (changedFileInputFullPath. Replace (input. The getAbsolutePath (), output.getAbsolutePath()));if(! changedFileOutput.exists()) { changedFileOutput.getParentFile().mkdirs(); } changedFileOutput.createNewFile();if(MethodCollector isNeedTraceFile (classFile getName ())) {/ / needed to pile is = new FileInputStream (classFile); ClassReader classReader = new ClassReader(is); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 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 ()); // Write the modified content to the inserted file. os.close(); }else{/ / don't need to pile, direct copy FileUtil. CopyFileUsingStream (classFile changedFileOutput); } } catch (Exception e) { } } }Copy the code
6.3 TraceClassAdapter
 private class TraceClassAdapter extends ClassVisitor {

        private String className;
        private boolean isABSClass = false;
        private boolean hasWindowFocusMethod = false; private boolean isActivityOrSubClass; private boolean isNeedTrace; TraceClassAdapter(int i, ClassVisitor classVisitor) { super(i, classVisitor); } @Override 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; This. / / whether the activity or subclass isActivityOrSubClass = isActivityOrSubClass (className, collectedClassExtendMap); / / this. Whether to need to be inserted pile isNeedTrace = MethodCollector. IsNeedTrace (configuration, the className, mappingCollector); // Is it an abstract class or interfaceif ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true; } } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {// Abstract classes and interfaces are not pluggedif (isABSClass) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                if(! HasWindowFocusMethod) {// Whether onWindowFocusChange hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc); } MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); // [see 6.4]return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
                        hasWindowFocusMethod, isActivityOrSubClass, isNeedTrace);
            }
        }


        @Override
        public void visitEnd() {// If the Activity subclass does not have onWindowFocusChange, insert an onWindowFocusChange methodif (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
                insertWindowFocusChangeMethod(cv, className);
            }
            super.visitEnd();
        }
    }
Copy the code
6.4 TraceMethodAdapter
private class TraceMethodAdapter extends AdviceAdapter { ..... // Add appMethodBeat.i () to the function entry @override protected voidonMethodEnter() {
            TraceMethod traceMethod = collectedMethodMap.get(methodName);
            if(traceMethod ! = null) { //traceMethodCount +1 traceMethodCount.incrementAndGet(); mv.visitLdcInsn(traceMethod.id); mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS,"i"."(I)V".false); Override protected void onMethodExit(int opcode) {TraceMethod TraceMethod = collectedMethodMap.get(methodName);if(traceMethod ! = null) {// Add appMethodBeat.at () to the exit of the onWindowFocusChanged methodif (hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
                    TraceMethod windowFocusChangeMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
                            TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
                    if (windowFocusChangeMethod.equals(traceMethod)) {
                        traceWindowFocusChangeMethod(mv, className);
                    }
                }

                //traceMethodCount +1
                traceMethodCount.incrementAndGet();
                mv.visitLdcInsn(traceMethod.id);
                mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o"."(I)V".false); }}}Copy the code
6.5 MethodTracer.traceMethodFromJar
    private void traceMethodFromJar(Map<File, File> dependencyMap, List<Future> futures) {
        if(null ! = dependencyMap) {for (Map.Entry<File, File> entry : dependencyMap.entrySet()) {
                futures.add(executor.submit(new Runnable() {
                    @Override
                    public void run() {
                        //【见6.6】
                        innerTraceMethodFromJar(entry.getKey(), entry.getValue());
                    }
                }));
            }
        }
    }
Copy the code
6.6 MethodTracer.innerTraceMethodFromJar
 private void innerTraceMethodFromJar(File input, File output) {
            Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
            while (enumeration.hasMoreElements()) {
                ZipEntry zipEntry = enumeration.nextElement();
                String zipEntryName = zipEntry.getName();
                if(MethodCollector.isNeedTraceFile(zipEntryName)) { InputStream inputStream = zipFile.getInputStream(zipEntry); ClassReader classReader = new ClassReader(inputStream); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); // 【 see 6.3】 ClassVisitor ClassVisitor = new TraceClassAdapter(opcodes.asm5, classWriter); classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES); byte[] data = classWriter.toByteArray(); InputStream byteArrayInputStream = new ByteArrayInputStream(data); ZipEntry newZipEntry = new ZipEntry(zipEntryName); FileUtil.addZipEntry(zipOutputStream, newZipEntry, byteArrayInputStream); }else{ InputStream inputStream = zipFile.getInputStream(zipEntry); ZipEntry newZipEntry = new ZipEntry(zipEntryName); Fileutil. addZipEntry(zipOutputStream, newZipEntry, inputStream); }}}Copy the code

7.1

To be continued.

conclusion

  1. Matrix hook system generates tasks of DEX as self-defined tasks during gradle evaluation period, and executes original tasks after completing relevant processes to return control to the system.
  2. Matrix uses Transform and ASM to complete the intrusion compilation process for bytecode insertion.

series

  • Tencent Apm framework Matrix source read – Gradle plug-in
  • Tencent Apm framework Matrix source code reading – architecture analysis

The resources

  • recommendedMatrix source complete annotation
  • Matrix Android TraceCanary
  • ————Trace Canary