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 文件
目录
相关文章
|
存储 缓存 Java
Android性能优化:内存管理与LeakCanary技术详解
【7月更文挑战第21天】内存管理是Android性能优化的关键部分,而LeakCanary则是进行内存泄漏检测和修复的强大工具。
|
缓存 JavaScript 前端开发
基于虚拟滚动的大型文档性能优化方案
基于虚拟滚动的大型文档性能优化方案旨在提高长列表或长文档的加载和滚动性能。虚拟滚动通过只渲染视口(用户可见区域)附近的元素来减少内存占用和渲染时间,而非一次性加载所有内容。
|
关系型数据库 MySQL Java
异常:no transaction is in progress
异常:no transaction is in progress
392 0
|
关系型数据库 MySQL Serverless
函数计算操作报错合集之当遇到“Cannot read properties of undefined(reading 'props')”错误,该怎么处理
在使用函数计算服务(如阿里云函数计算)时,用户可能会遇到多种错误场景。以下是一些常见的操作报错及其可能的原因和解决方法,包括但不限于:1. 函数部署失败、2. 函数执行超时、3. 资源不足错误、4. 权限与访问错误、5. 依赖问题、6. 网络配置错误、7. 触发器配置错误、8. 日志与监控问题。
1348 0
|
Java Android开发 开发者
《阿里巴巴Android开发手册》电子版地址
本手册以开发者为中心视角分为Java语言规范,Android资源文件命名与使用,Android基本组件,UI与布局等九大部分。
934 0
《阿里巴巴Android开发手册》电子版地址
|
监控 数据可视化 Java
Matrix原理分析系列之开篇
Matrix原理分析系列之开篇
717 0
Matrix原理分析系列之开篇
|
消息中间件 监控 算法
让 nativePollOnce 不再排名第一 | 钉钉 ANR 治理最佳实践
让 nativePollOnce 不再排名第一 | 钉钉 ANR 治理最佳实践
2332 0
让 nativePollOnce 不再排名第一 | 钉钉 ANR 治理最佳实践
|
Android开发 Windows 移动开发
Android 修改host文件的3种方法
Android修改hosts文件的方法介绍 本文介绍三种Android手机修改hosts文 件的方法,但修改hosts文件一定要谨慎:Android手机hosts文件的换行符必须是n而不是windows的rn,使用Notepad++打开 hosts文件,依次点击菜单中的“视图–显示符号–显示所有字符”,如果行末是LF就没问题,CR LF结束则需要替换所有的CR LF为LF。
9263 0
|
XML 前端开发 Android开发
Android 实现圆弧背景(Shape实现和自定义View)
如今Android系统的App,很多时候为了有更好的用户体验,都会有各种好看的UI,动画,点击效果等等,其中圆弧的控件在App中很常见,今儿就自己总结下自己实现圆弧的两种基础的方法。即Shape方法和使用View里面的方法自己画。
|
前端开发 Android开发 iOS开发
QMUI Android 该凉凉了
在微信听书最新的版本,累死累活的开发中,我还是把 Jetpack Compose 引入了工程中, 在新的原生界面开发中,用 Compose 来写 UI 了, 贼特么舒服,所以说, QMUI Android 要么重做出一个 Compose 版本,要么就该删库跑路了。
851 0