年前,微信开源了Matrix项目,提供了Android、ios的APM实现方案。对于Android端实现,主要包括APK Checker
、Resource Canary
、Trace Canary
、SQLite Lint
、IO Canary
五部分。本文主要介绍Trace Canary
的源码实现,其余部分的源码分析将在后续推出。java
Trace Canary经过字节码插桩的方式在编译期预埋了方法进入、方法退出的埋点。运行期,慢函数检测、FPS检测、卡顿检测、启动检测使用这些埋点信息排查具体哪一个函数致使的异常。android
代码插桩的总体流程如上图。在打包过程当中,hook生成Dex的Task任务,添加方法插桩的逻辑。咱们的hook点是在Proguard以后,Class已经被混淆了,因此须要考虑类混淆的问题。ios
插桩代码逻辑大体分为三步:git
hook原有的Task,执行本身的MatrixTraceTransform
,并在最后执行原逻辑github
在方法插桩以前先要读取ClassMapping文件,获取混淆前方法、混淆后方法的映射关系并存储在MappingCollector中。json
以后遍历全部Dir、Jar中的Class文件,实际代码执行的时候遍历了两次。后端
将实际执行的Transform换成了MatrixTraceTransform
api
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信息app
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)
}
复制代码
整体流程都在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);
}
}
}
复制代码
入口是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);
}
复制代码
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数量。
获取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,开发人员能够从方法级别来作优化。