騰訊 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 完成 侵入編譯流程進行字節碼插入操作。

系列文章

參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容