Matrix 之 TraceCanary 源码分析

简介: Matrix 之 TraceCanary 源码分析

今天来分析下 Matrix 框架的 matrix-trace-canary 部分,相关文档可查看《 Matrix Android TraceCanary 》,这份文档是阅读源码的关键,强烈建议先看文档再读源码。

TraceCanary 是检测应用卡顿的模块,在该模块下又细分了如下几个模块:

在这些模块当中,又是依赖 MethodBeat 的提前插桩来达到检测的效果,所以,我们在分析这几个模块之前,得先分析 MethodBeat 干了些什么,他是如何辅助以上几个模块来进行检测的。

为了不使文章变得枯燥无味,上面的四个模块会另起新的文章标题进行讲解,那么本文将会对 matrix-gradle-plugin 的插桩部分先进行一个大概的讲解,细节部分可能不会去分析,因为牵扯起来东西太多了。


来看下文档中对编译插桩部分的描述:


通过代理编译期间的任务 transformClassesWithDexTask,将全局 class 文件作为输入,利用 ASM 工具,高效地对所有 class 文件进行扫描及插桩。

插桩过程有几个关键点: 1、选择在该编译任务执行时插桩,是因为 proguard 操作是在该任务之前就完成的,意味着插桩时的 class 文件已经被混淆过的。而选择 proguard 之后去插桩,是因为如果提前插桩会造成部分方法不符合内联规则,没法在 proguard 时进行优化,最终导致程序方法数无法减少,从而引发方法数过大问题。

2、为了减少插桩量及性能损耗,通过遍历 class 方法指令集,判断扫描的函数是否只含有 PUT/READ FIELD 等简单的指令,来过滤一些默认或匿名构造函数,以及 get/set 等简单不耗时函数。

3、针对界面启动耗时,因为要统计从 Activity#onCreate 到 Activity#onWindowFocusChange 间的耗时,所以在插桩过程中需要收集应用内所有 Activity 的实现类,并覆盖 onWindowFocusChange 函数进行打点。

4、为了方便及高效记录函数执行过程,我们为每个插桩的函数分配一个独立 ID,在插桩过程中,记录插桩的函数签名及分配的 ID,在插桩完成后输出一份 mapping,作为数据上报后的解析支持

分析


apply plugin: 'om.tencent.matrix-plugin' 对应的是  matrix-gradle-plugin 中的MatrixPlugin 类:

implementation-class=com.tencent.matrix.plugin.MatrixPlugin

MatrixPlugin.java

image.png

该插件做了如下操作:


1、关联 apply MatrixPlugin 设置的 extensions 参数,该参数可以查看 sample 的 app build.gradle

2、插入 transfrom task,该 task 就如文档所描述的 transformClassesWithDexTask

3、移除未使用的资源文件

我们进入 MatrixTraceTransform.inject 看看:


MatrixTraceTransform.java


image.png

配置的部分我省略掉了,由于 sample app build.gradle 中未设置 customDexTransformName 的,所以,hardTask 会返回  transformClassesWithDexBuilderForDebug 和 transformClassesWithDexForDebug,在运行 sample 项目时,复制了所有的 build 过程的日志信息,在查找的过程中,只有 transformClassesWithDexBuilderForDebug 参与了活动,并且,也打印了如上的 successfully inject task:transformClassesWithDexBuilderForDebug  日志信息。


最后会 hook 掉 transformClassesWithDexBuilderForDebug 的 task,替换成自己的 MatrixTraceTransform,MatrixTraceTransform 的构造方法传入了 transformClassesWithDexBuilderForDebug task,在 MatrixTraceTransform 使用完成后,是需要传递给下一个 transfrom 继续运行的,所以,在 MatrixTraceTransform 的 transfrom 方法中,会看到:


origTransform.transform(transformInvocation);

这个 origTransform 就是系统的 transformClassesWithDexBuilderForDebug

这时候,我们可以直接来看 MatrixTraceTransform task 了, doTransfrom 是最重要的方法,这个部分我大概去讲解一下,因为涉及跳转太多,篇幅就会非常有影响,好在 Matrix 的开发者留给了我们三个 step 注释,我们就按照这三个步骤去解释:


image.png

1、step1:


这个地方需要关注几个 executor.submit 的执行,第一个 ParseMappingTask,这个 task 从名称上就能看出是一个解析 Mapping 的任务,这个任务做了如下的操作:


  1. 读取 output 下面的 mapping.txt 文件,还原混淆前的类和方法,存放在 MappingCollector 类中 mObfuscatedClassMethodMap 和 mOriginalClassMethodMap 集合中
  2. 读取自定义的 baseMethodMapFile 配置,直接生成 TraceMethod 存放到 collectedMethodMap 中,在 sample 中,baseMethodMapFile 未设置配置文件

接着看 CollectDirectoryInputTask 和 CollectJarInputTask 任务,这两个任务主要收集 class 原文件和 traceClassOut 文件,class 原文件我们都知道,就是扫描到的 class 文件,那 traceClassOut 文件是什么呢?在默认的配置中,会设置一份 traceClassOut 路径,这个配置路径指向的是 output/traceClassOut 路径,我们可以打开 sample 下面的这个路径:


image.png


2、step2


主要看 collect 方法,该方法做了如下的几个操作:

  1. 将 DirectoryInput 收集的 class 原文件和 CollectJarInputTask 收集的原文件进行遍历,利用 TraceClassAdapter 来收集需要插入字节码(collectedClassExtendMap)和类和需要忽略掉的类(collectedIgnoreMethodMap)
  2. 分类保存上面收集到的两个集合,在写入文件时,会根据原 class 混淆文件 revert 还原 class,然后一并写入到文件中,这样,我们在查看混淆与对应文件时会非常的方便:


collectedClassExtendMap 集合中的数据是需要做插桩的类,该集合中的数据保存在 output/mapping/methodMapping 下面:


image.png

collectedIgnoreMethodMap 集合中的数据是需要忽略掉的类,该集合中的数据保存在 output/mapping/ignoreMethodMapping 下面 :

image.png

3、step3


拿到混淆类集合和还原的类集合,然后拿到在 step1 中收集到的原文件和 traceOut 文件,一并参与 trace 操作。所做的内容有:

  1. 遍历原 class 文件,并创建 traceOut 路径文件
  2. 对原 class 文件进行插桩操作,插桩的内容有:
  3. Activity 的 onWindowFocusChanged 方法插入 AppMethodBeat.getInstance().at 方法
  4. 满足类的方法在开头和结尾插入 i/o 方法
  5. 对插桩后的 class 文件 copy 一份到 traceOut 路径文件

总结


补充部分:


  1. matrix-gradle-plugin 是可以设置黑名单的,在 sample app build.gradle 下可以看到设置的 balckMethodList,对于设置了黑名单的 package 或是 class,是不会被插桩操作
  2. 如果想看插桩之后的代码效果,可以直接查看 output/mapping/traceClassOut 路径下的效果类,确实很方便,建议做插桩效果的插件都能像 matrix 这样去做,在看实现效果时,再也不用去看 transfrom 下面各种各种的 jar 或是反编译 apk 才能看到自己是否真正被插入成功
  3. 对于未被插桩的函数,可以直接查看 output/mapping/ignoreMethodMapping.txt 文件
  4. 每个插桩函数都有对应的 methodId,如果想看对应关系的话,可以直接查看 output/mapping/methodMapping.txt 文件
目录
相关文章
|
8月前
|
存储 NoSQL API
[Eigen中文文档] Matrix类
在Eigen中,所有矩阵和向量都是Matrix模板类的对象。向量只是行数或者列数为1的特殊矩阵。
224 1
|
机器学习/深度学习 自然语言处理 算法
Word2Vec原理以及实战详解
Word2Vec原理以及实战详解
|
机器学习/深度学习 算法 搜索推荐
深入浅出理解word2vec模型 (理论与源码分析)
深入浅出理解word2vec模型 (理论与源码分析)
深入浅出理解word2vec模型 (理论与源码分析)
|
监控 数据可视化 Java
Matrix原理分析系列之开篇
Matrix原理分析系列之开篇
385 0
Matrix原理分析系列之开篇
|
索引
Matrix源码分析系列-如何解析应用安装包(二)
Matrix源码分析系列-如何解析应用安装包
59 0
|
存储 JSON 算法
Matrix源码分析系列-如何解析应用安装包(一)
Matrix源码分析系列-如何解析应用安装包
192 0
Matrix源码分析系列-如何解析应用安装包(一)
|
监控 Java 测试技术
Matrix源码分析系列-如何计算启动耗时(一)
Matrix源码分析系列-如何计算启动耗时
159 0
Matrix源码分析系列-如何计算启动耗时(一)
|
数据可视化
Matrix源码分析系列-如何计算启动耗时(二)
Matrix源码分析系列-如何计算启动耗时
146 0
Matrix源码分析系列-如何计算启动耗时(二)
|
存储
Matrix 之 StartupTracer 源码分析
Matrix 之 StartupTracer 源码分析
187 0
Matrix 之 StartupTracer 源码分析
|
存储 Java API
Matrix 之 ResourceCanary 源码分析
Matrix 之 ResourceCanary 源码分析
215 0
Matrix 之 ResourceCanary 源码分析