内容简介:年前,微信开源了Trace Canary通过字节码插桩的方式在编译期预埋了方法进入、方法退出的埋点。运行期,慢函数检测、FPS检测、卡顿检测、启动检测使用这些埋点信息排查具体哪个函数导致的异常。代码插桩的整体流程如上图。在打包过程中,hook生成Dex的Task任务,添加方法插桩的逻辑。我们的hook点是在Proguard之后,Class已经被混淆了,所以需要考虑类混淆的问题。
年前,微信开源了 Matrix 项目,提供了Android、ios的APM实现方案。对于Android端实现,主要包括 APK Checker
、 Resource Canary
、 Trace Canary
、 SQLite Lint
、 IO Canary
五部分。本文主要介绍 Trace Canary
的源码实现,其他部分的源码分析将在后续推出。
代码框架分析
Trace Canary通过字节码插桩的方式在编译期预埋了方法进入、方法退出的埋点。运行期,慢函数检测、FPS检测、卡顿检测、启动检测使用这些埋点信息排查具体哪个函数导致的异常。
编译期方法插桩代码分析
代码插桩的整体流程如上图。在打包过程中,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插桩逻辑
入口是 MethodTracer
的 trace
方法
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的映射关系,追查到有问题的方法
慢函数检测
目的:检测影响主线程执行的慢函数。
上文讲述了在编译期,会对每个方法的执行体前后添加上 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) { ... } } 复制代码
统计了当应用处于前台时,在主线程执行的方法的进入、退出。这些信息最后存储在 MethodBeat
的 Buffer
中。当主线程有疑似慢函数存在时,读取 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,开发人员可以从方法级别来做优化。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图片转BASE64编码
在线图片转Base64编码工具
URL 编码/解码
URL 编码/解码