以前说到,Matrix 的卡顿监控关键在于插桩,下面来看一下它是怎么实现的。java
Matrix 的 Gradle 插件的实现类为 MatrixPlugin,主要作了三件事:android
class MatrixPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.create("matrix", MatrixExtension)
project.matrix.extensions.create("trace", MatrixTraceExtension)
project.matrix.extensions.create("removeUnusedResources", MatrixDelUnusedResConfiguration)
}
}
复制代码
其中 trace 可选配置以下:markdown
public class MatrixTraceExtension {
boolean enable; // 是否启用插桩功能
String baseMethodMapFile; // 自定义的方法映射文件,下面会说到
String blackListFile; // 该文件指定的方法不会被插桩
String customDexTransformName;
}
复制代码
removeUnusedResources 可选配置以下:app
class MatrixDelUnusedResConfiguration {
boolean enable // 是否启用
String variant // 指定某一个构建变体启用插桩功能,若是为空,则全部的构建变体都启用
boolean needSign // 是否须要签名
boolean shrinkArsc // 是否裁剪 arsc 文件
String apksignerPath // 签名文件的路径
Set<String> unusedResources // 指定要删除的不使用的资源
Set<String> ignoreResources // 指定不须要删除的资源
}
复制代码
// 在编译期执行插桩任务(project.afterEvaluate 表明 build.gradle 文件执行完毕),这是由于 proguard 操做是在该任务以前就完成的
project.afterEvaluate {
android.applicationVariants.all { variant ->
if (configuration.trace.enable) { // 是否启用,可在 gradle 文件中配置
MatrixTraceTransform.inject(project, configuration.trace, variant.getVariantData().getScope())
}
... // RemoveUnusedResourcesTask
}
}
复制代码
MatrixTraceTransform 的 inject 方法主要用于读取配置,代理 transformClassesWithDexTask:ide
public class MatrixTraceTransform extends Transform {
public static void inject(Project project, MatrixTraceExtension extension, VariantScope variantScope) {
... // 根据参数生成 Configuration 变量 config
String[] hardTask = getTransformTaskName(extension.getCustomDexTransformName(), variant.getName());
for (Task task : project.getTasks())
for (String str : hardTask)
if (task.getName().equalsIgnoreCase(str) && task instanceof TransformTask) {
Field field = TransformTask.class.getDeclaredField("transform");
field.set(task, new MatrixTraceTransform(config, task.getTransform()));
break;
}
}
// 这两个 Transform 用于把 Class 文件编译成 Dex 文件
// 所以,须要在这两个 Transform 执行以前完成插桩等工做
private static String[] getTransformTaskName(String customDexTransformName, String buildTypeSuffix) {
return new String[] {
"transformClassesWithDexBuilderFor" + buildTypeSuffix,
"transformClassesWithDexFor" + buildTypeSuffix,
};;
}
}
复制代码
MatrixTraceTransform 的主要配置以下:gradle
public class MatrixTraceTransform extends Transform {
@Override
public Set<QualifiedContent.ContentType> getInputTypes() { return TransformManager.CONTENT_CLASS; }
@Override
public Set<QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT; }
}
复制代码
transform 主要分三步执行:优化
private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
// 用于分析和方法统计相关的文件,如 mapping.txt、blackMethodList.txt 等
// 并将映射规则保存到 mappingCollector、collectedMethodMap 中
futures.add(executor.submit(new ParseMappingTask(mappingCollector, collectedMethodMap, methodId)));
}
复制代码
private void doTransform(TransformInvocation transformInvocation) {
MethodCollector methodCollector = new MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap);
methodCollector.collect(dirInputOutMap.keySet(), jarInputOutMap.keySet());
}
复制代码
private void doTransform(TransformInvocation transformInvocation) {
MethodTracer methodTracer = new MethodTracer(executor, mappingCollector, config,
methodCollector.getCollectedMethodMap(), methodCollector.getCollectedClassExtendMap());
methodTracer.trace(dirInputOutMap, jarInputOutMap);
}
复制代码
ParseMappingTask 主要用于分析方法统计相关的文件,如 mapping.txt(ProGuard 生成的)、blackMethodList.txt 等,并将映射规则保存到 HashMap 中。ui
mapping.txt 是 ProGuard 生成的,用于映射混淆先后的类名/方法名,内容以下:lua
MTT.ThirdAppInfoNew -> MTT.ThirdAppInfoNew: // oldClassName -> newClassName
java.lang.String sAppName -> sAppName // oldMethodName -> newMethodName
java.lang.String sTime -> sTime
...
复制代码
blackMethodList.txt 则用于避免对特定的方法插桩,内容以下:spa
[package]
-keeppackage com/huluxia/logger/
-keepmethod com/example/Application attachBaseContext (Landroid/content/Context;)V
...
复制代码
若是有须要,还能够指定 baseMethodMapFile,将自定义的方法及其对应的方法 id 写入到一个文件中,内容格式以下:
// 方法 id、访问标志、类名、方法名、描述
1,1,eu.chainfire.libsuperuser.Application$1 run ()V
2,9,eu.chainfire.libsuperuser.Application toast (Landroid.content.Context;Ljava.lang.String;)V
复制代码
上述选项可在 gradle 文件配置,示例以下:
matrix {
trace {
enable = true
baseMethodMapFile = "{projectDir.absolutePath}/baseMethodMapFile.txt"
blackListFile = "{projectDir.absolutePath}/blackMethodList.txt"
}
}
复制代码
顾名思义,MethodCollector 用于收集方法,它首先会把方法封装为 TraceMethod,并分配方法 id,再保存到 HashMap,最后写入到文件中。
为此,首先须要获取全部 class 文件:
public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException {
for (File srcFile : srcFolderList) {
...
for (File classFile : classFileList) {
futures.add(executor.submit(new CollectSrcTask(classFile)));
}
}
for (File jarFile : dependencyJarList) {
futures.add(executor.submit(new CollectJarTask(jarFile)));
}
}
复制代码
接着,借助 ASM 访问每个 Class 文件:
class CollectSrcTask implements Runnable {
@Override
public void run() {
InputStream is = new FileInputStream(classFile);
ClassReader classReader = new ClassReader(is);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
classReader.accept(visitor, 0);
}
}
复制代码
及 Class 文件中的方法:
private class TraceClassAdapter extends ClassVisitor {
@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 {
return new CollectMethodNode(className, access, name, desc, signature, exceptions);
}
}
}
复制代码
最后,记录方法数据,并保存到 HashMap 中:
private class CollectMethodNode extends MethodNode {
@Override
public void visitEnd() {
super.visitEnd();
// 将方法数据封装为 TraceMethod
TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
// 是否须要插桩,blackMethodList.txt 中指定的方法不会被插桩
boolean isNeedTrace = isNeedTrace(configuration, traceMethod.className, mappingCollector);
// 过滤空方法、get & set 方法等简单方法
if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod()) && isNeedTrace) {
return;
}
// 保存到 HashMap 中
if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
traceMethod.id = methodId.incrementAndGet();
collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
incrementCount.incrementAndGet();
} else if (!isNeedTrace && !collectedIgnoreMethodMap.containsKey(traceMethod.className)) {
... // 记录不须要插桩的方法
}
}
}
复制代码
统计完毕后,将上述方法及其 ID 写入到一个文件中——由于以后上报问题只会上报 method id,所以须要根据该文件来解析具体的方法名及其耗时。
虽然上面的代码很长,但做用实际很简单:访问全部 Class 文件中的方法,记录方法 ID,并写入到文件中。
须要注意的细节有:
和方法统计同样,插桩也是基于 ASM 实现的,首先一样要找到全部 Class 文件,再针对文件中的每个方法进行处理。
处理流程主要包含四步:
public final static String MATRIX_TRACE_CLASS = "com/tencent/matrix/trace/core/AppMethodBeat";
private class TraceMethodAdapter extends AdviceAdapter {
@Override
protected void onMethodEnter() {
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) { // 省略空方法、set & get 等简单方法
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
}
}
}
复制代码
private class TraceMethodAdapter extends AdviceAdapter {
@Override
protected void onMethodExit(int opcode) {
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) {
... // 跟踪 onWindowFocusChanged 方法,计算启动耗时
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
}
}
}
复制代码
private class TraceClassAdapter extends ClassVisitor {
@Override
public void visitEnd() {
// 若是是 Activity,而且不存在 onWindowFocusChanged 方法,则插入该方法,用于统计 Activity 启动时间
if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
insertWindowFocusChangeMethod(cv, className);
}
super.visitEnd();
}
}
复制代码
public final static String MATRIX_TRACE_CLASS = "com/tencent/matrix/trace/core/AppMethodBeat";
private void traceWindowFocusChangeMethod(MethodVisitor mv, String classname) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "at", "(Landroid/app/Activity;Z)V", false);
}
复制代码
public class AppMethodBeat implements BeatLifecycle {
public static void at(Activity activity, boolean isFocus) {
for (IAppMethodBeatListener listener : listeners) {
listener.onActivityFocused(activityName);
}
}
}
复制代码
StartupTracer 就是 IAppMethodBeatListener 的实现类。
Matrix 的 Gradle 插件的实现类为 MatrixPlugin,主要作了三件事:
须要注意的是,插桩任务是在编译期执行的,这是为了不对混淆操做产生影响。由于 proguard 操做是在该任务以前就完成的,意味着插桩时的 class 文件已经被混淆过的。而选择 proguard 以后去插桩,是由于若是提早插桩会形成部分方法不符合内联规则,无法在 proguard 时进行优化,最终致使程序方法数没法减小,从而引起方法数过大问题
transform 主要分三步执行:
插桩处理流程主要包含四步:
值得注意的细节有: