騰訊 Apm 框架 Matrix 源碼閱讀 - gradle插件

版本

v0.6.5

溫馨提示

配合 推薦 Matrix 源碼完整注釋
可能會有更好的效果

概述

本篇文章是 騰訊開源的 APM 框架 Matrix 系列文章的開篇循捺,將對 matrix-trace-canary這個模塊的代碼進行閱讀顿苇。我們知道 gradle plugin 的入口肯定是繼承了 Plugin 的類舱馅,在 matrix-trace-canary 中就對應的是 MatrixPlugin ,下面我們就從這個類開始閱讀。

1. MatrixPlugin

MatrixPlugin 只有apply一個方法片择,該方法做了 創(chuàng)建Extension 和 注冊 Task兩件事

    void apply(Project project) {
        //創(chuàng)建 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了 系統(tǒng)構(gòu)建Dex 的 Transform 并配合 ASM 框架 ,插入方法執(zhí)行時間記錄的字節(jié)碼邦蜜,這一系列內(nèi)容將是本文的重點依鸥。

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的 關(guān)系
                .setIgnoreMethodMapFilePath(mappingOut + "/ignoreMethodMapping.txt")// 記錄 沒有被 插樁的方法
                .setMappingPath(mappingOut) //mapping文件存儲目錄
                .setTraceClassOut(traceClassOut)//插樁后的 class存儲目錄
                .build();

        try {
            // 獲取 TransformTask.. 具體名稱 如:transformClassesWithDexBuilderForDebug 和 transformClassesWithDexForDebug
            // 具體是哪一個 應該和 gradle的版本有關(guān)
            // 在該 task之前  proguard 操作 已經(jīng)完成
            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);
                        // 將 系統(tǒng)的  "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 構(gòu)造方法

MatrixTraceTransform 中保存了悼沈,被hook的 Transform 贱迟,因為需要執(zhí)行完 MatrixTraceTransform 的內(nèi)容后,再恢復原流程絮供。

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

3. MatrixTraceTransform.transform

上面將的MatrixTraceTransform.inject()發(fā)生在gradle 的評估期 衣吠,也就是說在評估期已經(jīng)確認了 整個 gradle Task的執(zhí)行順序,在運行期的話 gradle 會回調(diào) 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();
        }
        ...
        //執(zhí)行原來應該執(zhí)行的 Transform 的 transform 方法
        origTransform.transform(transformInvocation);
        ...
     
    }
3.1 MatrixTraceTransform.doTransform

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

  1. 解析mapping 文件記錄 混淆前后方法的對應關(guān)系 并 替換文件目錄
  2. 收集需要插樁和不需要插樁的方法記錄在 mapping文件中 并 收集類之間的繼承關(guān)系
  3. 進行字節(jié)碼插樁
    private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
        //是否增量編譯
        final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental();

         /**
         * step 1 
         * 1. 解析mapping 文件混淆后方法對應關(guān)系
         * 2. 替換文件目錄
         */
        long start = System.currentTimeMillis();

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

        // 存儲 混淆前方法、混淆后方法的映射關(guān)系
        final MappingCollector mappingCollector = new MappingCollector();
        // methodId 計數(shù)器
        final AtomicInteger methodId = new AtomicInteger(0);
        // 存儲 需要插樁的 方法名 和 方法的封裝對象TraceMethod
        final ConcurrentHashMap<String, TraceMethod> collectedMethodMap = new ConcurrentHashMap<>();

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

        //存放原始 源文件 和 輸出 源文件的 對應關(guān)系
        Map<File, File> dirInputOutMap = new ConcurrentHashMap<>();

        //存放原始jar文件和 輸出jar文件 對應關(guān)系
        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. 收集類之間的繼承關(guān)系
         */
        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 插樁字節(jié)碼
         */
        start = System.currentTimeMillis();
        //執(zhí)行插樁邏輯恬惯,在需要插樁方法的入口、出口添加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();
            //創(chuàng)建輸出文件
            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();

                //保存輸出文件和其狀態(tài)的 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輸出已經(jīng)重定向到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();
            //創(chuàng)建唯一的 文件
            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 的映射關(guān)系
    private final ConcurrentHashMap<String, TraceMethod> collectedIgnoreMethodMap = new ConcurrentHashMap<>();
    //存儲 需要插樁方法名 -> 該方法TraceMethod 的映射關(guān)系
    private final ConcurrentHashMap<String, TraceMethod> collectedMethodMap;
    private final Configuration configuration;
    private final AtomicInteger methodId;
    // 被忽略方法計數(shù)器
    private final AtomicInteger ignoreCount = new AtomicInteger();
    //需要插樁方法 計數(shù)器
    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) {
                // 每個源文件執(zhí)行 CollectSrcTask  【見5.2】
                futures.add(executor.submit(new CollectSrcTask(classFile)));
            }
        }

        for (File jarFile : dependencyJarList) {
            // 每個jar 源文件執(zhí)行 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 框架 發(fā)揮作用的時候了沾凄,ASM 在掃描類的時候 會依次回調(diào) 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中執(zhí)行method收集操作 【見5.4】
                return new CollectMethodNode(className, access, name, desc, signature, exceptions);
            }
        }
    }
5.4 CollectMethodNode

CollectMethodNode繼承了MethodNode ,ASM框架在掃描方法的時候會回調(diào) MethodNode 中的 visitEnd 方法

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

            //如果是構(gòu)造方法
            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 一樣都會 調(diào)用到 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 方法很簡單保屯,就是將前面手機的 被忽略的方法內(nèi)容寫到 ignoreMethodMapping.txt 中

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

        //創(chuàng)建 ignoreMethodMapping.txt 文件對象
        File methodMapFile = new File(configuration.ignoreMethodMapFilePath);
        //如果他爸不存在就創(chuàng)建
        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) {
                //將 混淆過的數(shù)據(jù) 轉(zhuǎn)換為 原始數(shù)據(jù)
                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 方法的執(zhí)行時間
        //所以將這個例外 加進去
        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);
                    }
                    //將修改后的內(nèi)容寫入到 插裝后的文件中
                    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 {

       .....

        //函數(shù)入口處添加 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);
            }
        }

       

        //函數(shù)出口處添加 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 到插裝過后的 存放區(qū)
                    FileUtil.addZipEntry(zipOutputStream, newZipEntry, inputStream);
                }
            }
}

7.1

未完待續(xù)切蟋。统捶。

總結(jié)

  1. Matrix 在 gradle 的評估期 hook 系統(tǒng) 生成dex 的 Task 為自定義的 Task,并在執(zhí)行完相關(guān)流程后柄粹,再執(zhí)行回原有Task喘鸟,將控制權(quán)交還給系統(tǒng)。
  2. Matrix 使用 Transform 配合 ASM 完成 侵入編譯流程進行字節(jié)碼插入操作驻右。

系列文章

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末什黑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子堪夭,更是在濱河造成了極大的恐慌兑凿,老刑警劉巖凯力,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異礼华,居然都是意外死亡,警方通過查閱死者的電腦和手機拗秘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雕旨,“玉大人,你說我怎么就攤上這事棒搜。” “怎么了力麸?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵育韩,是天一觀的道長。 經(jīng)常有香客問我筋讨,道長,這世上最難降的妖魔是什么悉罕? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮类早,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘莺奔。我一直安慰自己变泄,他們只是感情好令哟,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妨蛹,像睡著了一般屏富。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛙卤,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天狠半,我揣著相機與錄音噩死,去河邊找鬼。 笑死神年,一個胖子當著我的面吹牛已维,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播已日,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼垛耳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了飘千?” 一聲冷哼從身側(cè)響起堂鲜,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎护奈,沒想到半個月后缔莲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡霉旗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年痴奏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奖慌。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡抛虫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出简僧,到底是詐尸還是另有隱情建椰,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布岛马,位于F島的核電站,受9級特大地震影響啦逆,放射性物質(zhì)發(fā)生泄漏夏志。R本人自食惡果不足惜沟蔑,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一瘦材、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧食棕,春花似錦、人聲如沸眶拉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屋休。三九已至劫樟,卻和暖如春叠艳,著一層夾襖步出監(jiān)牢的瞬間附较,已是汗流浹背拒课。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工早像, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卢鹦,地道東北人冀自。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓凡纳,卻偏偏與公主長得像荐糜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子延塑,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容