Matrix源码分析————Trace Canary

栏目: IOS · Android · 发布时间: 6年前

内容简介:年前,微信开源了Trace Canary通过字节码插桩的方式在编译期预埋了方法进入、方法退出的埋点。运行期,慢函数检测、FPS检测、卡顿检测、启动检测使用这些埋点信息排查具体哪个函数导致的异常。代码插桩的整体流程如上图。在打包过程中,hook生成Dex的Task任务,添加方法插桩的逻辑。我们的hook点是在Proguard之后,Class已经被混淆了,所以需要考虑类混淆的问题。

年前,微信开源了 Matrix 项目,提供了Android、ios的APM实现方案。对于Android端实现,主要包括 APK CheckerResource CanaryTrace CanarySQLite LintIO Canary 五部分。本文主要介绍 Trace Canary 的源码实现,其他部分的源码分析将在后续推出。

代码框架分析

Trace Canary通过字节码插桩的方式在编译期预埋了方法进入、方法退出的埋点。运行期,慢函数检测、FPS检测、卡顿检测、启动检测使用这些埋点信息排查具体哪个函数导致的异常。

Matrix源码分析————Trace Canary

编译期方法插桩代码分析

Matrix源码分析————Trace Canary

代码插桩的整体流程如上图。在打包过程中,hook生成Dex的Task任务,添加方法插桩的逻辑。我们的hook点是在Proguard之后,Class已经被混淆了,所以需要考虑类混淆的问题。

插桩代码逻辑大致分为三步:

  • hook原有的Task,执行自己的 MatrixTraceTransform ,并在最后执行原逻辑

  • 在方法插桩之前先要读取ClassMapping文件,获取混淆前方法、混淆后方法的映射关系并存储在MappingCollector中。

  • 之后遍历所有Dir、Jar中的Class文件,实际代码执行的时候遍历了两次。

    • 第一次遍历Class,获取所有待插桩的Method信息,并将信息输出到methodMap文件中;
    • 第二次遍历Class,利用ASM执行Method插桩逻辑。

hook原生打包流程

将实际执行的Transform换成了 MatrixTraceTransform

public static void inject(Project project, def variant) {
        //获取Matrix trace的gradle配置参数
        def configuration = project.matrix.trace
        //hook的Task名
        String hackTransformTaskName = getTransformTaskName(
                configuration.hasProperty('customDexTransformName') ? configuration.customDexTransformName : "",
                "",variant.name
        )
        //同上
        String hackTransformTaskNameForWrapper = getTransformTaskName(
                configuration.hasProperty('customDexTransformName') ? configuration.customDexTransformName : "",
                "Builder",variant.name
        )

        project.logger.info("prepare inject dex transform :" + hackTransformTaskName +" hackTransformTaskNameForWrapper:"+hackTransformTaskNameForWrapper)

        project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
            @Override
            public void graphPopulated(TaskExecutionGraph taskGraph) {
                for (Task task : taskGraph.getAllTasks()) {
                    //找到需要hook的Task名称
                    if ((task.name.equalsIgnoreCase(hackTransformTaskName) || task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))
                            && !(((TransformTask) task).getTransform() instanceof MatrixTraceTransform)) {
                        project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name)
                        project.logger.info("variant name: " + variant.name)
                        Field field = TransformTask.class.getDeclaredField("transform")
                        field.setAccessible(true)
                        //反射替换成MatrixTraceTransform,并将原transform传入,最后执行原transform逻辑
                        field.set(task, new MatrixTraceTransform(project, variant, task.transform))
                        project.logger.warn("transform class after hook: " + task.transform.getClass())
                        break
                    }
                }
            }
        })
    }
复制代码

MatrixTraceTransform 主要逻辑在 transform 方法中

@Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        long start = System.currentTimeMillis()
        //是否增量编译
        final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental()
        //transform的结果,重定向输出到这个目录
        final File rootOutput = new File(project.matrix.output, "classes/${getName()}/")
        if (!rootOutput.exists()) {
            rootOutput.mkdirs()
        }
        final TraceBuildConfig traceConfig = initConfig()
        Log.i("Matrix." + getName(), "[transform] isIncremental:%s rootOutput:%s", isIncremental, rootOutput.getAbsolutePath())
        //获取Class混淆的mapping信息,存储到mappingCollector中
        final MappingCollector mappingCollector = new MappingCollector()
        File mappingFile = new File(traceConfig.getMappingPath());
        if (mappingFile.exists() && mappingFile.isFile()) {
            MappingReader mappingReader = new MappingReader(mappingFile);
            mappingReader.read(mappingCollector)
        }

        Map<File, File> jarInputMap = new HashMap<>()
        Map<File, File> scrInputMap = new HashMap<>()

        transformInvocation.inputs.each { TransformInput input ->
            input.directoryInputs.each { DirectoryInput dirInput ->
                //收集、重定向目录中的class
                collectAndIdentifyDir(scrInputMap, dirInput, rootOutput, isIncremental)
            }
            input.jarInputs.each { JarInput jarInput ->
                if (jarInput.getStatus() != Status.REMOVED) {
                    //收集、重定向jar包中的class
                    collectAndIdentifyJar(jarInputMap, scrInputMap, jarInput, rootOutput, isIncremental)
                }
            }
        }
        //收集需要插桩的方法信息,每个插桩信息封装成TraceMethod对象
        MethodCollector methodCollector = new MethodCollector(traceConfig, mappingCollector)
        HashMap<String, TraceMethod> collectedMethodMap = methodCollector.collect(scrInputMap.keySet().toList(), jarInputMap.keySet().toList())
       //执行插桩逻辑,在需要插桩方法的入口、出口添加MethodBeat的i/o逻辑
        MethodTracer methodTracer = new MethodTracer(traceConfig, collectedMethodMap, methodCollector.getCollectedClassExtendMap())
        methodTracer.trace(scrInputMap, jarInputMap)
        //执行原transform的逻辑;默认transformClassesWithDexBuilderForDebug这个task会将Class转换成Dex
        origTransform.transform(transformInvocation)
        Log.i("Matrix." + getName(), "[transform] cost time: %dms", System.currentTimeMillis() - start)
    }
复制代码

收集Dir中的Class信息

private void collectAndIdentifyDir(Map<File, File> dirInputMap, DirectoryInput input, File rootOutput, boolean isIncremental) {
        final File dirInput = input.file
        final File dirOutput = new File(rootOutput, input.file.getName())
        if (!dirOutput.exists()) {
            dirOutput.mkdirs()
        }
        //增量编译
        if (isIncremental) {
            if (!dirInput.exists()) {
                dirOutput.deleteDir()
            } else {
                final Map<File, Status> obfuscatedChangedFiles = new HashMap<>()
                final String rootInputFullPath = dirInput.getAbsolutePath()
                final String rootOutputFullPath = dirOutput.getAbsolutePath()
                input.changedFiles.each { Map.Entry<File, Status> entry ->
                    final File changedFileInput = entry.getKey()
                    final String changedFileInputFullPath = changedFileInput.getAbsolutePath()
                    //增量编译模式下之前的build输出已经重定向到dirOutput;替换成output的目录
                    final File changedFileOutput = new File(
                            changedFileInputFullPath.replace(rootInputFullPath, rootOutputFullPath)
                    )
                    final Status status = entry.getValue()
                    switch (status) {
                        case Status.NOTCHANGED:
                            break
                        case Status.ADDED:
                        case Status.CHANGED:
                            //新增、修改的Class文件,此次需要扫描
                            dirInputMap.put(changedFileInput, changedFileOutput)
                            break
                        case Status.REMOVED:
                            //删除的Class文件,将文件直接删除
                            changedFileOutput.delete()
                            break
                    }
                    obfuscatedChangedFiles.put(changedFileOutput, status)
                }
                replaceChangedFile(input, obfuscatedChangedFiles)
            }
        } else {
            //全量编译模式下,所有的Class文件都需要扫描
            dirInputMap.put(dirInput, dirOutput)
        }
        //反射input,将dirOutput设置为其输出目录
        replaceFile(input, dirOutput)
    }
复制代码

反射替换输出目录的代码:

protected void replaceFile(QualifiedContent input, File newFile) {
        final Field fileField = ReflectUtil.getDeclaredFieldRecursive(input.getClass(), 'file')
        fileField.set(input, newFile
    }
复制代码

类似的,收集Jar中的Class信息

private void collectAndIdentifyJar(Map<File, File> jarInputMaps, Map<File, File> dirInputMaps, JarInput input, File rootOutput, boolean isIncremental) {
        final File jarInput = input.file
        final File jarOutput = new File(rootOutput, getUniqueJarName(jarInput))
        if (IOUtil.isRealZipOrJar(jarInput)) {
            switch (input.status) {
                case Status.NOTCHANGED:
                    if (isIncremental) {
                        break
                    }
                case Status.ADDED:
                case Status.CHANGED:
                    jarInputMaps.put(jarInput, jarOutput)
                    break
                case Status.REMOVED:
                    break
            }
        } else {
            ...
            //这部分代码可忽略,微信AutoDex自定义的文件结构
        }

        replaceFile(input, jarOutput)
    }
复制代码

第一次遍历Class,收集待插桩method

总体流程都在 collect 方法中

public HashMap collect(List<File> srcFolderList, List<File> dependencyJarList) {
        mTraceConfig.parseBlackFile(mMappingCollector);
        //获取base模块已经收集到的待插桩方法
        File originMethodMapFile = new File(mTraceConfig.getBaseMethodMap());
        getMethodFromBaseMethod(originMethodMapFile);
        
        Log.i(TAG, "[collect] %s method from %s", mCollectedMethodMap.size(), mTraceConfig.getBaseMethodMap());
        //转换为混淆后的方法名
        retraceMethodMap(mMappingCollector, mCollectedMethodMap);
        //仅收集目录、jar包中的class信息
        collectMethodFromSrc(srcFolderList, true);
        collectMethodFromJar(dependencyJarList, true);
        //收集目录、jar包中的method信息
        collectMethodFromSrc(srcFolderList, false);
        collectMethodFromJar(dependencyJarList, false);
        Log.i(TAG, "[collect] incrementCount:%s ignoreMethodCount:%s", mIncrementCount, mIgnoreCount);
        //存储待插桩的方法信息到文件
        saveCollectedMethod(mMappingCollector);
        //存储不需要插桩的方法信息到文件(包括黑名单中的方法)
        saveIgnoreCollectedMethod(mMappingCollector);
        //返回待插桩的方法集合
        return mCollectedMethodMap;

    }
复制代码

收集method信息的逻辑类似,以下面代码为例(字节码相关操作使用了ASM)

private void innerCollectMethodFromSrc(File srcFile, boolean isSingle) {
        ArrayList<File> classFileList = new ArrayList<>();
        if (srcFile.isDirectory()) {
            listClassFiles(classFileList, srcFile);
        } else {
            classFileList.add(srcFile);
        }

        for (File classFile : classFileList) {
            InputStream is = null;
            try {
                is = new FileInputStream(classFile);
                ClassReader classReader = new ClassReader(is);
                ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                ClassVisitor visitor;
                if (isSingle) {
                    //仅收集Class信息
                    visitor = new SingleTraceClassAdapter(Opcodes.ASM5, classWriter);
                } else {
                    //收集Method信息
                    visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
                }
                classReader.accept(visitor, 0);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    is.close();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    }
复制代码

个人感觉 SingleTraceClassAdapter 好像是多余的,一个 TraceClassAdapter 可以搞定收集Class、Method的信息

private class TraceClassAdapter extends ClassVisitor {
        private String className;
        private boolean isABSClass = false;
        private boolean hasWindowFocusMethod = false;

        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;
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }
            //存储一个 类->父类 的map(用于查找Activity的子类)
            mCollectedClassExtendMap.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 = mTraceConfig.isWindowFocusChangeMethod(name, desc);
                }
                //CollectMethodNode中执行method收集操作
                return new CollectMethodNode(className, access, name, desc, signature, exceptions);
            }
        }

        @Override
        public void visitEnd() {
            super.visitEnd();
            // collect Activity#onWindowFocusChange
            //onWindowFocusChange方法统一给一个-1的方法id
            TraceMethod traceMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
                    TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
            //没有过复写onWindowFocusChange,后续会在该类中插入一个onWindowFocusChange方法,此处先记录一下这个会被插桩的方法
            if (!hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap) && mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)) {
                mCollectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
            }
        }

    }
复制代码

如果子类Activity复写了onWindowFocusChange方法,其对应的methodId就不为-1了;这块逻辑感觉有点问题~~

private class CollectMethodNode extends MethodNode {
        private String className;
        private boolean isConstructor;


        CollectMethodNode(String className, int access, String name, String desc,
                          String signature, String[] exceptions) {
            super(Opcodes.ASM5, access, name, desc, signature, exceptions);
            this.className = className;
        }

        @Override
        public void visitEnd() {
            super.visitEnd();
            TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);

            if ("<init>".equals(name) /*|| "<clinit>".equals(name)*/) {
                isConstructor = true;
            }
            // filter simple methods
            //忽略空方法、get/set方法、没有局部变量的简单方法,
            if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
                    && mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)) {
                mIgnoreCount++;
                mCollectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
                return;
            }

            //不在黑名单中的方法加入待插桩的集合;在黑名单中的方法加入ignore插桩的集合
            if (mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector) && !mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
                traceMethod.id = mMethodId.incrementAndGet();
                mCollectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
                mIncrementCount++;
            } else if (!mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)
                    && !mCollectedBlackMethodMap.containsKey(traceMethod.className)) {
                mIgnoreCount++;
                mCollectedBlackMethodMap.put(traceMethod.getMethodName(), traceMethod);
            }

        }
    }
复制代码

第二次遍历Class,执行method插桩逻辑

入口是 MethodTracertrace 方法

public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) {
        traceMethodFromSrc(srcFolderList);
        traceMethodFromJar(dependencyJarList);
    }
复制代码

分别对目录、jar包插桩

private void innerTraceMethodFromSrc(File input, File output) {

        ...
                if (mTraceConfig.isNeedTraceClass(classFile.getName())) {
                    is = new FileInputStream(classFile);
                    ClassReader classReader = new ClassReader(is);
                    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                    ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
                    classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
        ...
    }
    
    private void innerTraceMethodFromJar(File input, File output) {
       ...
                if (mTraceConfig.isNeedTraceClass(zipEntryName)) {
                    InputStream inputStream = zipFile.getInputStream(zipEntry);
                    ClassReader classReader = new ClassReader(inputStream);
                    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                    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);
    ...
    }
复制代码

核心逻辑在 TraceClassAdapter

private class TraceClassAdapter extends ClassVisitor {

        private String className;
        private boolean isABSClass = false;
        private boolean isMethodBeatClass = false;
        private boolean hasWindowFocusMethod = false;

        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;
            //是否是抽象类、接口
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }
            //是否是MethodBeat类
            if (mTraceConfig.isMethodBeatClass(className, mCollectedClassExtendMap)) {
                isMethodBeatClass = 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 = mTraceConfig.isWindowFocusChangeMethod(name, desc);
                }
                MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
                return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
                        hasWindowFocusMethod, isMethodBeatClass);
            }
        }


        @Override
        public void visitEnd() {
            TraceMethod traceMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
                    TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
            //如果Activity的子类没有onWindowFocusChange方法,插入一个onWindowFocusChange方法
            if (!hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap)
                    && mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
                insertWindowFocusChangeMethod(cv);
            }
            super.visitEnd();
        }
    }
复制代码

在待插桩方法的入口、出口添加对应逻辑

rivate class TraceMethodAdapter extends AdviceAdapter {

        private final String methodName;
        private final String name;
        private final String className;
        private final boolean hasWindowFocusMethod;
        private final boolean isMethodBeatClass;

        protected TraceMethodAdapter(int api, MethodVisitor mv, int access, String name, String desc, String className,
                                     boolean hasWindowFocusMethod, boolean isMethodBeatClass) {
            super(api, mv, access, name, desc);
            TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
            this.methodName = traceMethod.getMethodName();
            this.isMethodBeatClass = isMethodBeatClass;
            this.hasWindowFocusMethod = hasWindowFocusMethod;
            this.className = className;
            this.name = name;
        }

        @Override
        protected void onMethodEnter() {
            TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
            if (traceMethod != null) {
                //函数入口处添加逻辑;
                //没有单独处理onWindowFocusChange,对于已经复写onWindowFocusChange的Activity子类,会有问题?
                traceMethodCount.incrementAndGet();
                mv.visitLdcInsn(traceMethod.id);
                mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
            }
        }

        @Override
        protected void onMethodExit(int opcode) {
            TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
            if (traceMethod != null) {
                if (hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap)
                        && mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
                    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)) {
                        //onWindowFocusChange方法统一添加method id = -1的逻辑
                        traceWindowFocusChangeMethod(mv);
                    }
                }
                //函数出口处添加逻辑
                traceMethodCount.incrementAndGet();
                mv.visitLdcInsn(traceMethod.id);
                mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
            }
        }
    }
复制代码

对于没有复现onWindowFocusChange方法的Activity子类,插入一个onWindowFocusChange方法

private void insertWindowFocusChangeMethod(ClassVisitor cv) {
        MethodVisitor methodVisitor = cv.visitMethod(Opcodes.ACC_PUBLIC, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD,
                TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS, null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitVarInsn(Opcodes.ILOAD, 1);
        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, TraceBuildConstants.MATRIX_TRACE_ACTIVITY_CLASS, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD,
                TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS, false);
        traceWindowFocusChangeMethod(methodVisitor);
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(2, 2);
        methodVisitor.visitEnd();

    }
复制代码

至此,编译期插桩的逻辑就结束了;在运行期,检测到某个方法异常时,会上报一个method id,后端通过下图的method id到method name的映射关系,追查到有问题的方法

Matrix源码分析————Trace Canary

慢函数检测

目的:检测影响主线程执行的慢函数。

上文讲述了在编译期,会对每个方法的执行体前后添加上 MethodBeat.i(int methodId)MethodBeat.o(int methodId) 的方法调用,且methodId是在编译期生成的,在运行时是一个写死的常量。通过编译期的这个操作,就能感知到具体每个方法的进入、退出动作。下面来看下这两个方法的内部实现

/**
     * hook method when it's called in.
     *
     * @param methodId
     */
    public static void i(int methodId) {
        if (isBackground) {
            return;
        }
        ...
        isRealTrace = true;
        if (isCreated && Thread.currentThread() == sMainThread) {
           ...
        } else if (!isCreated && Thread.currentThread() == sMainThread && sBuffer != null) {
           ..
        }
    }

    /**
     * hook method when it's called out.
     *
     * @param methodId
     */
    public static void o(int methodId) {
        if (isBackground || null == sBuffer) {
            return;
        }
        if (isCreated && Thread.currentThread() == sMainThread) {
            ...
        } else if (!isCreated && Thread.currentThread() == sMainThread) {
            ...
        }
    }
复制代码

统计了当应用处于前台时,在主线程执行的方法的进入、退出。这些信息最后存储在 MethodBeatBuffer 中。当主线程有疑似慢函数存在时,读取 Buffer 的数据,分析可能的慢函数,并上报json数据到后端(后端将methodId转换为具体的方法声明)。

疑似发生慢函数的实际有两个:一个是掉帧的场景,一个是类似ANR这样长时间主线程阻塞UI绘制的场景。

  • 掉帧的场景

内部 FrameBeat 类实现了 Choreographer.FrameCallback ,可以感知每一帧的绘制时间。通过前后两帧的时间差判断是否有慢函数发生。

@Override
    public void doFrame(long lastFrameNanos, long frameNanos) {
        if (isIgnoreFrame) {
            mActivityCreatedInfoMap.clear();
            setIgnoreFrame(false);
            getMethodBeat().resetIndex();
            return;
        }

        int index = getMethodBeat().getCurIndex();
        //判断是否有慢函数
        if (hasEntered && frameNanos - lastFrameNanos > mTraceConfig.getEvilThresholdNano()) {
            MatrixLog.e(TAG, "[doFrame] dropped frame too much! lastIndex:%s index:%s", 0, index);
            handleBuffer(Type.NORMAL, 0, index - 1, getMethodBeat().getBuffer(), (frameNanos - lastFrameNanos) / Constants.TIME_MILLIS_TO_NANO);
        }
        getMethodBeat().resetIndex();
        mLazyScheduler.cancel();
        mLazyScheduler.setUp(this, false);

    }
复制代码
  • 主线程长时间阻塞UI绘制的场景

LazyScheduler 内有一个HandlerThread,调用 LazyScheduler.setup 方法会向这个HandlerThread的MQ发送一个延时5s的消息。若没有发生类似ANR的场景,在每一帧的 doFrame 回调中取消这个消息,同时发送一个新的延时5s的消息(正常情况下消息是得不到执行的);若发生类似ANR的情况, doFrame 没有被回调,这个延时5s的消息得到执行,将回调到 onTimeExpire 方法

@Override
    public void onTimeExpire() {
        // maybe ANR
        if (isBackground()) {
            MatrixLog.w(TAG, "[onTimeExpire] pass this time, on Background!");
            return;
        }
        long happenedAnrTime = getMethodBeat().getCurrentDiffTime();
        MatrixLog.w(TAG, "[onTimeExpire] maybe ANR!");
        setIgnoreFrame(true);
        getMethodBeat().lockBuffer(false);
        //有慢函数
        handleBuffer(Type.ANR, 0, getMethodBeat().getCurIndex() - 1, getMethodBeat().getBuffer(), null, Constants.DEFAULT_ANR, happenedAnrTime, -1);
    }
复制代码

当检测到慢函数时,会在后台线程完成慢函数的分析

private final class AnalyseTask implements Runnable {

        private final long[] buffer;
        private final AnalyseExtraInfo analyseExtraInfo;

        private AnalyseTask(long[] buffer, AnalyseExtraInfo analyseExtraInfo) {
            this.buffer = buffer;
            this.analyseExtraInfo = analyseExtraInfo;
        }

        private long getTime(long trueId) {
            return trueId & 0x7FFFFFFFFFFL;
        }

        private int getMethodId(long trueId) {
            return (int) ((trueId >> 43) & 0xFFFFFL);
        }

        private boolean isIn(long trueId) {
            return ((trueId >> 63) & 0x1) == 1;
        }

        @Override
        public void run() {
            analyse(buffer);
        }

        private void analyse(long[] buffer) {
            ...
            //分析逻辑主要是找出最耗时的方法,可自行阅读

}
复制代码

FPS检测

目的:检测绘制过程中的FPS数量。

获取DectorView的ViewTreeObserver,感知UI绘制的开始

private void addDrawListener(final Activity activity) {
        activity.getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                activity.getWindow().getDecorView().getViewTreeObserver().removeOnDrawListener(FPSTracer.this);
                activity.getWindow().getDecorView().getViewTreeObserver().addOnDrawListener(FPSTracer.this);
            }
        });
    }
    
      @Override
    public void onDraw() {
        isDrawing = true;
    }

复制代码

通过 Choreographer.FrameCallback ,感知UI绘制的结束

@Override
    public void doFrame(long lastFrameNanos, long frameNanos) {
        if (!isInvalid && isDrawing && isEnterAnimationComplete() && mTraceConfig.isTargetScene(getScene())) {
            handleDoFrame(lastFrameNanos, frameNanos, getScene());
        }
        isDrawing = false;
    }
复制代码

理论上用户更关心的是绘制过程中FPS过低导致的卡顿(UI静止的情况下,用户是感知不到FPS低的)

doFrame 方法中,记录每一帧的数据,其中 scene 这个字段标识一个页面

@Override
    public void onChange(final Activity activity, final Fragment fragment) {
        this.mScene = TraceConfig.getSceneForString(activity, fragment);
    }
复制代码

onChange 的默认实现是通过Application的 ActivityLifecycleCallbacks 回调感知Activity的变化

@Override
    public void onActivityResumed(final Activity activity) {
        ...
        if (!activityHash.equals(mCurActivityHash)) {
            for (IObserver listener : mObservers) {
                listener.onChange(activity, null);
            }
            mCurActivityHash = activityHash;
        }
        ...
    }

复制代码

FPS数据默认是2分钟分析一次(前台情况下),切后台时后台轮询线程停止。

/**
     * report FPS
     */
    private void doReport() {
        ...
        //数据分析逻辑可行阅读
    }
复制代码

卡顿检测

目的:检测UI绘制过程中的卡顿情况。

卡顿检测与FPS检测类似,在每一帧的`doFrame回调中判断是否有卡顿发生,如有卡顿将数据发送到后台分析线程处理。

@Override
    public void doFrame(final long lastFrameNanos, final long frameNanos) {
        if (!isDrawing) {
            return;
        }
        isDrawing = false;
        final int droppedCount = (int) ((frameNanos - lastFrameNanos) / REFRESH_RATE_MS);
        if (droppedCount > 1) {
            for (final IDoFrameListener listener : mDoFrameListenerList) {
                listener.doFrameSync(lastFrameNanos, frameNanos, getScene(), droppedCount);
                if (null != listener.getHandler()) {
                    listener.getHandler().post(new Runnable() {
                        @Override
                        public void run() {
                            listener.getHandler().post(new AsyncDoFrameTask(listener,
                                    lastFrameNanos, frameNanos, getScene(), droppedCount));
                        }
                    });

                }
            }
复制代码

启动检测

目的:检测启动阶段耗时

应用启动时,会直接对ActivityThread类hook

public class Hacker {
    private static final String TAG = "Matrix.Hacker";
    public static boolean isEnterAnimationComplete = false;
    public static long sApplicationCreateBeginTime = 0L;
    public static int sApplicationCreateBeginMethodIndex = 0;
    public static long sApplicationCreateEndTime = 0L;
    public static int sApplicationCreateEndMethodIndex = 0;
    public static int sApplicationCreateScene = -100;

    public static void hackSysHandlerCallback() {
        try {
            //这个类被加载的时间,认为是整个App的启动开始时间
            sApplicationCreateBeginTime = System.currentTimeMillis();
            sApplicationCreateBeginMethodIndex = MethodBeat.getCurIndex();
            Class<?> forName = Class.forName("android.app.ActivityThread");
            Field field = forName.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object activityThreadValue = field.get(forName);
            Field mH = forName.getDeclaredField("mH");
            mH.setAccessible(true);
            Object handler = mH.get(activityThreadValue);
            Class<?> handlerClass = handler.getClass().getSuperclass();
            Field callbackField = handlerClass.getDeclaredField("mCallback");
            callbackField.setAccessible(true);
            Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
            HackCallback callback = new HackCallback(originalCallback);
            callbackField.set(handler, callback);
            MatrixLog.i(TAG, "hook system handler completed. start:%s", sApplicationCreateBeginTime);
        } catch (Exception e) {
            MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
        }
    }
}
复制代码

代理原有的Handler.Callback,感知Application onCreate的结束时间

public class HackCallback implements Handler.Callback {
    private static final String TAG = "Matrix.HackCallback";
    private static final int LAUNCH_ACTIVITY = 100;
    private static final int ENTER_ANIMATION_COMPLETE = 149;
    private static final int CREATE_SERVICE = 114;
    private static final int RECEIVER = 113;
    private static boolean isCreated = false;

    private final Handler.Callback mOriginalCallback;

    public HackCallback(Handler.Callback callback) {
        this.mOriginalCallback = callback;
    }

    @Override
    public boolean handleMessage(Message msg) {
//        MatrixLog.i(TAG, "[handleMessage] msg.what:%s begin:%s", msg.what, System.currentTimeMillis());
        if (msg.what == LAUNCH_ACTIVITY) {
            Hacker.isEnterAnimationComplete = false;
        } else if (msg.what == ENTER_ANIMATION_COMPLETE) {
            Hacker.isEnterAnimationComplete = true;
        }
        if (!isCreated) {
            if (msg.what == LAUNCH_ACTIVITY || msg.what == CREATE_SERVICE || msg.what == RECEIVER) {
                //发送启动Activity等消息,认为是Application onCreate的结束时间
                Hacker.sApplicationCreateEndTime = System.currentTimeMillis();
                Hacker.sApplicationCreateEndMethodIndex = MethodBeat.getCurIndex();
                Hacker.sApplicationCreateScene = msg.what;
                isCreated = true;
            }
        }
        if (null == mOriginalCallback) {
            return false;
        }
        return mOriginalCallback.handleMessage(msg);
    }
}
复制代码

记录第一个Activity的onCreate时间

@Override
    public void onActivityCreated(Activity activity) {
        super.onActivityCreated(activity);
        if (isFirstActivityCreate && mFirstActivityMap.isEmpty()) {
            String activityName = activity.getComponentName().getClassName();
            mFirstActivityIndex = getMethodBeat().getCurIndex();
            mFirstActivityName = activityName;
            mFirstActivityMap.put(activityName, System.currentTimeMillis());
            MatrixLog.i(TAG, "[onActivityCreated] first activity:%s index:%s", mFirstActivityName, mFirstActivityIndex);
            getMethodBeat().lockBuffer(true);
        }
    }
复制代码

记录Activity获取焦点的时间(在编译期,在Activity子类的onWindowFocusChange方法中插入 MethodBeat.at 方法)

public static void at(Activity activity, boolean isFocus) {
        MatrixLog.i(TAG, "[AT] activity: %s, isCreated: %b sListener size: %d,isFocus: %b",
                activity.getClass().getSimpleName(), isCreated, sListeners.size(), isFocus);
        if (isCreated && Thread.currentThread() == sMainThread) {
            for (IMethodBeatListener listener : sListeners) {
                listener.onActivityEntered(activity, isFocus, sIndex - 1, sBuffer);
            }
        }
    }
复制代码

当Activity获取到焦点时,认为启动阶段结束(若有SplashActivity,则记录下一个Activity获取焦点的时间)

@Override
    public void onActivityEntered(Activity activity, boolean isFocus, int nowIndex, long[] buffer) {
       ...启动数据分析
    }
复制代码

总结

Matrix Trace检测巧妙的利用了编译期字节码插桩技术,优化了移动端的FPS、卡顿、启动的检测手段;借助Matrix Trace,开发人员可以从方法级别来做优化。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Think Python

Think Python

Allen B. Downey / O'Reilly Media / 2012-8-23 / GBP 29.99

Think Python is an introduction to Python programming for students with no programming experience. It starts with the most basic concepts of programming, and is carefully designed to define all terms ......一起来看看 《Think Python》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具