腾讯 Apm 框架 Matrix 源码阅读 - gradle插件

版本

v0.6.5

温馨提示

配合 推荐 Matrix 源码完整注释
可能会有更好的效果

概述

本篇文章是 腾讯开源的 APM 框架 Matrix 系列文章的开篇,将对 matrix-trace-canary这个模块的代码进行阅读。我们知道 gradle plugin 的入口肯定是继承了 Plugin 的类,在 matrix-trace-canary 中就对应的是 MatrixPlugin ,下面我们就从这个类开始阅读。

1. MatrixPlugin

MatrixPlugin 只有apply一个方法,该方法做了 创建Extension 和 注册 Task两件事

    void apply(Project project) {
        //创建 extension
        project.extensions.create("matrix", MatrixExtension)
        project.matrix.extensions.create("trace", MatrixTraceExtension)
        project.matrix.extensions.create("removeUnusedResources", MatrixDelUnusedResConfiguration)
        ....
        project.afterEvaluate {
          ....
            android.applicationVariants.all { variant ->

                if (configuration.trace.enable) {
                    //注入MatrixTraceTransform 【见2.1】
                    com.tencent.matrix.trace.transform.MatrixTraceTransform.inject(project, configuration.trace, variant.getVariantData().getScope())
                }

                //移除无用资源 可用 【见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
                    }
                }

            }
        }
    }

2. MatrixTraceTransform

MatrixTraceTransform 继承了 Transform, 该类中hook了 系统构建Dex 的 Transform 并配合 ASM 框架 ,插入方法执行时间记录的字节码,这一系列内容将是本文的重点。

2.1 MatrixTraceTransform.inject
   public static void inject(Project project, MatrixTraceExtension extension, VariantScope variantScope) {
        ...
        //收集配置信息
        Configuration config = new Configuration.Builder()
                .setPackageName(variant.getApplicationId())//包名
                .setBaseMethodMap(extension.getBaseMethodMapFile())//build.gradle 中配置的 baseMethodMapFile ,保存的是 我们指定需要被 插桩的方法
                .setBlackListFile(extension.getBlackListFile())//build.gradle 中配置的 blackListFile ,保存的是 不需要插桩的文件
                .setMethodMapFilePath(mappingOut + "/methodMapping.txt")// 记录插桩 methodId 和 method的 关系
                .setIgnoreMethodMapFilePath(mappingOut + "/ignoreMethodMapping.txt")// 记录 没有被 插桩的方法
                .setMappingPath(mappingOut) //mapping文件存储目录
                .setTraceClassOut(traceClassOut)//插桩后的 class存储目录
                .build();

        try {
            // 获取 TransformTask.. 具体名称 如:transformClassesWithDexBuilderForDebug 和 transformClassesWithDexForDebug
            // 具体是哪一个 应该和 gradle的版本有关
            // 在该 task之前  proguard 操作 已经完成
            String[] hardTask = getTransformTaskName(extension.getCustomDexTransformName(), variant.getName());
            for (Task task : project.getTasks()) {
                for (String str : hardTask) {
                    // 找到 task 并进行 hook
                    if (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);
                        // 将 系统的  "transformClassesWithDexBuilderFor.."和"transformClassesWithDexFor.."
                        // 中的 transform 替换为 MatrixTraceTransform(也就是当前类) 【见2.2】
                        field.set(task, new MatrixTraceTransform(config, transformTask.getTransform()));
                        break;
                    }
                }
            }
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }

    }
2.2 MatrixTraceTransform 构造方法

MatrixTraceTransform 中保存了,被hook的 Transform ,因为需要执行完 MatrixTraceTransform 的内容后,再恢复原流程。

    public MatrixTraceTransform(Configuration config, Transform origTransform) {
        //配置
        this.config = config;
        //原始Transform 也就是被 hook的 Transform
        this.origTransform = origTransform;
    }

3. MatrixTraceTransform.transform

上面将的MatrixTraceTransform.inject()发生在gradle 的评估期 ,也就是说在评估期已经确认了 整个 gradle Task的执行顺序,在运行期的话 gradle 会回调 Transform 的 transform方法,下面我么一起来看看。

    @Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation);
       ...
        try {
             //  【见3.1】
            doTransform(transformInvocation);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        ...
        //执行原来应该执行的 Transform 的 transform 方法
        origTransform.transform(transformInvocation);
        ...
     
    }
3.1 MatrixTraceTransform.doTransform

doTransform 方法的功能可被分为三步

  1. 解析mapping 文件记录 混淆前后方法的对应关系 并 替换文件目录
  2. 收集需要插桩和不需要插桩的方法记录在 mapping文件中 并 收集类之间的继承关系
  3. 进行字节码插桩
    private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
        //是否增量编译
        final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental();

         /**
         * step 1 
         * 1. 解析mapping 文件混淆后方法对应关系
         * 2. 替换文件目录
         */
        long start = System.currentTimeMillis();

        List<Future> futures = new LinkedList<>();

        // 存储 混淆前方法、混淆后方法的映射关系
        final MappingCollector mappingCollector = new MappingCollector();
        // methodId 计数器
        final AtomicInteger methodId = new AtomicInteger(0);
        // 存储 需要插桩的 方法名 和 方法的封装对象TraceMethod
        final ConcurrentHashMap<String, TraceMethod> collectedMethodMap = new ConcurrentHashMap<>();

        // 将 ParseMappingTask 放入线程池
        futures.add(executor.submit(new ParseMappingTask(mappingCollector, collectedMethodMap, methodId)));

        //存放原始 源文件 和 输出 源文件的 对应关系
        Map<File, File> dirInputOutMap = new ConcurrentHashMap<>();

        //存放原始jar文件和 输出jar文件 对应关系
        Map<File, File> jarInputOutMap = new ConcurrentHashMap<>();
        Collection<TransformInput> inputs = transformInvocation.getInputs();

        for (TransformInput input : inputs) {

            for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
                 //【见4.1】
                futures.add(executor.submit(new CollectDirectoryInputTask(dirInputOutMap, directoryInput, isIncremental)));
            }

            for (JarInput inputJar : input.getJarInputs()) {
                 //【见4.3】
                futures.add(executor.submit(new CollectJarInputTask(inputJar, isIncremental, jarInputOutMap, dirInputOutMap)));
            }
        }

        for (Future future : futures) {
            // 等待所有线程 运行完毕
            future.get();
        }
        //清空任务
        futures.clear();

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


        /**
         * step 2
         * 1. 收集需要插桩和不需要插桩的方法,并记录在 mapping文件中
         * 2. 收集类之间的继承关系
         */
        start = System.currentTimeMillis();
        //收集需要插桩的方法信息,每个插桩信息封装成TraceMethod对象
        MethodCollector methodCollector = new MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap);
          //【见5.1】
        methodCollector.collect(dirInputOutMap.keySet(), jarInputOutMap.keySet());
        Log.i(TAG, "[doTransform] Step(2)[Collection]... cost:%sms", System.currentTimeMillis() - start);

        /**
         * step 3 插桩字节码
         */
        start = System.currentTimeMillis();
        //执行插桩逻辑,在需要插桩方法的入口、出口添加MethodBeat的i/o逻辑
        MethodTracer methodTracer = new MethodTracer(executor, mappingCollector, config, methodCollector.getCollectedMethodMap(), methodCollector.getCollectedClassExtendMap());
          //【见6.1】
        methodTracer.trace(dirInputOutMap, jarInputOutMap);
        Log.i(TAG, "[doTransform] Step(3)[Trace]... cost:%sms", System.currentTimeMillis() - start);

    }

4. CollectDirectoryInputTask

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

该方法会通过反射 修改 输入文件的 属性,在增量编译模式下会修改 file
changedFiles两个属性 ,在全量编译模式下 只会修改 file 这一个属性

 private void handle() throws IOException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
            //获取原始文件
            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) {//增量更新,只 操作有改动的文件
                Map<File, Status> fileStatusMap = directoryInput.getChangedFiles();

                //保存输出文件和其状态的 map
                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();
                    //增量编译模式下之前的build输出已经重定向到dirOutput;替换成output的目录
                    final File changedFileOutput = new File(changedFileInputFullPath.replace(inputFullPath, outputFullPath));

                    if (status == Status.ADDED || status == Status.CHANGED) {
                        //新增、修改的Class文件,此次需要扫描
                        dirInputOutMap.put(changedFileInput, changedFileOutput);
                    } else if (status == Status.REMOVED) {
                        //删除的Class文件,将文件直接删除
                        changedFileOutput.delete();
                    }
                    outChangedFiles.put(changedFileOutput, status);
                }

                //使用反射 替换directoryInput的  改动文件目录
                replaceChangedFile(directoryInput, outChangedFiles);

            } else {
                //全量编译模式下,所有的Class文件都需要扫描
                dirInputOutMap.put(dirInput, dirOutput);
            }
            //反射input,将dirOutput设置为其输出目录
            replaceFile(directoryInput, dirOutput);
        }
4.3 CollectJarInputTask.run

CollectJarInputTask的工作和 CollectDirectoryInputTask基本上是一样的,只不过操作目标从文件夹换成了 jar

        @Override
        public void run() {
            try {
                【见4.4】
                handle();
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("Matrix." + getName(), "%s", e.toString());
            }
        }
4.4 CollectJarInputTask.handle
 private void handle() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, IOException {
            // traceClassOut 文件夹地址
            String traceClassOut = config.traceClassOut;

            final File jarInput = inputJar.getFile();
            //创建唯一的 文件
            final File jarOutput = new File(traceClassOut, getUniqueJarName(jarInput));
            ....

            if (IOUtil.isRealZipOrJar(jarInput)) {
                if (isIncremental) {//是增量
                    if (inputJar.getStatus() == Status.ADDED || inputJar.getStatus() == Status.CHANGED) {
                        //存放到 jarInputOutMap 中
                        jarInputOutMap.put(jarInput, jarOutput);
                    } else if (inputJar.getStatus() == Status.REMOVED) {
                        jarOutput.delete();
                    }

                } else {
                    //存放到 jarInputOutMap 中
                    jarInputOutMap.put(jarInput, jarOutput);
                }

            } else {// 专门用于 处理 WeChat AutoDex.jar 文件 可以略过,意义不大
               ....
            }

            //将 inputJar 的 file 属性替换为 jarOutput
            replaceFile(inputJar, jarOutput);

        }

5. MethodCollector

5.1 MethodCollector.collect
    //存储 类->父类 的map(用于查找Activity的子类)
    private final ConcurrentHashMap<String, String> collectedClassExtendMap = new ConcurrentHashMap<>();
    //存储 被忽略方法名 -> 该方法TraceMethod 的映射关系
    private final ConcurrentHashMap<String, TraceMethod> collectedIgnoreMethodMap = new ConcurrentHashMap<>();
    //存储 需要插桩方法名 -> 该方法TraceMethod 的映射关系
    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 原始文件集合
     * @param dependencyJarList 原始 jar 集合
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException {
        List<Future> futures = new LinkedList<>();

        for (File srcFile : srcFolderList) {
            //将所有源文件添加到 classFileList 中
            ArrayList<File> classFileList = new ArrayList<>();
            if (srcFile.isDirectory()) {
                listClassFiles(classFileList, srcFile);
            } else {
                classFileList.add(srcFile);
            }

            //这里应该是个bug,这个for 应该防止撒谎给你吗那个for 的外面
            for (File classFile : classFileList) {
                // 每个源文件执行 CollectSrcTask  【见5.2】
                futures.add(executor.submit(new CollectSrcTask(classFile)));
            }
        }

        for (File jarFile : dependencyJarList) {
            // 每个jar 源文件执行 CollectJarTask  【见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() {
                //存储不需要插桩的方法信息到文件(包括黑名单中的方法) 【见5.6】
                saveIgnoreCollectedMethod(mappingCollector);
            }
        }));

        futures.add(executor.submit(new Runnable() {
            @Override
            public void run() {
                //存储待插桩的方法信息到文件  【见5.7】
                saveCollectedMethod(mappingCollector);
            }
        }));

        for (Future future : futures) {
            future.get();
        }
        futures.clear();

    }
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);
                //收集Method信息  【见5.3】
                ClassVisitor visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
                classReader.accept(visitor, 0);

            } catch (Exception e) {
            ...
        }
5.3 TraceClassAdapter.visit

TraceClassAdapter 类的时候就到了 ASM 框架 发挥作用的时候了,ASM 在扫描类的时候 会依次回调 visitvisitMethod 方法

    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;
            //如果是虚拟类或者接口 isABSClass =true
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }
            //存到 collectedClassExtendMap 中
            collectedClassExtendMap.put(className, superName);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                                         String signature, String[] exceptions) {
            if (isABSClass) {//如果是虚拟类或者接口 就不管
                return super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                if (!hasWindowFocusMethod) {
                    //该方法是否与onWindowFocusChange方法的签名一致
                    // (该类中是否复写了onWindowFocusChange方法,Activity不用考虑Class混淆)
                    hasWindowFocusMethod = isWindowFocusChangeMethod(name, desc);
                }
                //CollectMethodNode中执行method收集操作 【见5.4】
                return new CollectMethodNode(className, access, name, desc, signature, exceptions);
            }
        }
    }
5.4 CollectMethodNode

CollectMethodNode继承了MethodNode ,ASM框架在扫描方法的时候会回调 MethodNode 中的 visitEnd 方法

private class CollectMethodNode extends MethodNode {
        ....
        @Override
        public void visitEnd() {
            super.visitEnd();
            //创建TraceMethod
            TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);

            //如果是构造方法
            if ("<init>".equals(name)) {
                isConstructor = true;
            }

            //判断类是否 被配置在了 黑名单中
            boolean isNeedTrace = isNeedTrace(configuration, traceMethod.className, mappingCollector);
            //忽略空方法、get/set方法、没有局部变量的简单方法
            if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
                    && isNeedTrace) {
                //忽略方法递增
                ignoreCount.incrementAndGet();
                //加入到被忽略方法 map
                collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
                return;
            }

            //不在黑名单中而且没在在methodMapping中配置过的方法加入待插桩的集合;
            if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
                traceMethod.id = methodId.incrementAndGet();
                collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
                incrementCount.incrementAndGet();
            } else if (!isNeedTrace && !collectedIgnoreMethodMap.containsKey(traceMethod.className)) {//在黑名单中而且没在在methodMapping中配置过的方法加入ignore插桩的集合
                ignoreCount.incrementAndGet();
                collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
            }

        }
.....
}
5.5 CollectJarTask.run

CollectJarTaskCollectSrcTask 一样都会 调用到 TraceClassAdapter进行方法的扫描

    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);
                        //进行扫描 【见5.3】
                        ClassVisitor visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
                        classReader.accept(visitor, 0);
                    }
                }
            }
        ....
5.6 MethodCollector.saveIgnoreCollectedMethod

saveIgnoreCollectedMethod 方法很简单,就是将前面手机的 被忽略的方法内容写到 ignoreMethodMapping.txt 中

 /**
     * 将被忽略的 方法名 存入 ignoreMethodMapping.txt 中
     * @param mappingCollector
     */
    private void saveIgnoreCollectedMethod(MappingCollector mappingCollector) {

        //创建 ignoreMethodMapping.txt 文件对象
        File methodMapFile = new File(configuration.ignoreMethodMapFilePath);
        //如果他爸不存在就创建
        if (!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());

        //通过class名字进行排序
        Collections.sort(ignoreMethodList, new Comparator<TraceMethod>() {
            @Override
            public int compare(TraceMethod o1, TraceMethod o2) {
                return o1.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:");
            for (TraceMethod traceMethod : ignoreMethodList) {
                //将 混淆过的数据 转换为 原始数据
                traceMethod.revert(mappingCollector);
                //输出忽略信息到 文件中
                pw.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();
            }
        }
    }
5.7 MethodCollector.saveCollectedMethod

saveCollectedMethod是将需要插桩的方法写入 methodMapping.txt

 /**
     * 将被插桩的 方法名 存入 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<>();

        //因为Android包下的 都不会被插装,但是我们需要 dispatchMessage 方法的执行时间
        //所以将这个例外 加进去
        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());

        //通过ID 进行排序
        Collections.sort(methodList, new Comparator<TraceMethod>() {
            @Override
            public int compare(TraceMethod o1, TraceMethod o2) {
                return o1.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();
            }
        }
    }

6. MethodTracer.trace

trace 方法就是真正开始插桩

    public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) throws ExecutionException, InterruptedException {
        List<Future> futures = new LinkedList<>();
        //对源文件进行插桩 【见6.1】
        traceMethodFromSrc(srcFolderList, futures);
        //对jar进行插桩 【见6.5】
        traceMethodFromJar(dependencyJarList, futures);
        for (Future future : futures) {
            future.get();
        }
        futures.clear();
    }
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() {
                        //【见6.2】
                        innerTraceMethodFromSrc(entry.getKey(), entry.getValue());
                    }
                }));
            }
        }
    }
6.2 MethodTracer.innerTraceMethodFromSrc
private void innerTraceMethodFromSrc(File input, File output) {
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);
                    // TraceClassAdapter 进行插桩 【见6.3】
                    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 {//不需要插桩,直接copy
                    FileUtil.copyFileUsingStream(classFile, changedFileOutput);
                }
            } catch (Exception e) {
     }
}
}
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;
            //是否是 activity 或者其 子类
            this.isActivityOrSubClass = isActivityOrSubClass(className, collectedClassExtendMap);
            //是否需要被插桩
            this.isNeedTrace = MethodCollector.isNeedTrace(configuration, className, mappingCollector);
            //是否是抽象类、接口
            if ((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) {

            //抽象类、接口不插桩
            if (isABSClass) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                if (!hasWindowFocusMethod) {
                    //是否是onWindowFocusChange方法
                    hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc);
                }
                MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
                //【见6.4】
                return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
                        hasWindowFocusMethod, isActivityOrSubClass, isNeedTrace);
            }
        }


        @Override
        public void visitEnd() {
            //如果Activity的子类没有onWindowFocusChange方法,插入一个onWindowFocusChange方法
            if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
                insertWindowFocusChangeMethod(cv, className);
            }
            super.visitEnd();
        }
    }
6.4 TraceMethodAdapter
 private class TraceMethodAdapter extends AdviceAdapter {

       .....

        //函数入口处添加 AppMethodBeat.i()方法
        @Override
        protected void onMethodEnter() {
            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);
            }
        }

       

        //函数出口处添加 AppMethodBeat.O()方法
        @Override
        protected void onMethodExit(int opcode) {
            TraceMethod traceMethod = collectedMethodMap.get(methodName);
            if (traceMethod != null) {
                //是 onWindowFocusChanged 方法 则在出口添加 AppMethodBeat.at()
                if (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);
            }
        }
    }
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());
                    }
                }));
            }
        }
    }
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);
                    // 【见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);
                    //直接copy jar 到插装过后的 存放区
                    FileUtil.addZipEntry(zipOutputStream, newZipEntry, inputStream);
                }
            }
}

7.1

未完待续。。

总结

  1. Matrix 在 gradle 的评估期 hook 系统 生成dex 的 Task 为自定义的 Task,并在执行完相关流程后,再执行回原有Task,将控制权交还给系统。
  2. Matrix 使用 Transform 配合 ASM 完成 侵入编译流程进行字节码插入操作。

系列文章

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容