AGP 实现方法插桩探究

简介: AGP 实现方法插桩探究

市面上已经有很多实现方法插桩的框架了,为什么我们还要重复造轮子呢?由于业务拓展时刻在变化,我们不得不去造一个适应业务的轮子,在造轮子之前,我们先列几个比较热门的框架进行比较,列出优缺点,然后再总结自己实现的轮子

方案比较


框架 性能 技术实现 方法参数获取 混淆 范围
hugo aspectJ 支持 不支持 java 类
costTime asm 不支持 支持 app 模块
matrix asm 不支持 支持 所有模块
Mamba asm 支持 支持 所有模块

1、hugo


hugo 是利用 aspectJ 实现的方法插桩,使用很简单,只需要给方法添加 @DebugLog 注解即可获取方法的执行耗时。由于使用的是 aspectJ,只能作用于 java 文件,对于 aar 文件不起作用,并且,获取方法和参数的整个过程非常耗时,具体可以看 enterMethod 和 exitMethod 方法。hugo 也不支持混淆,codeSignature.getName 拿到的是混淆后的方法,无法拿到原函数名,这也就无法做收集统计。可能 hugo 的定位仅仅只是 debug 阶段的统计,从注解 DebugLog 就可以看出。

2、costTime


这是巴神写的一个统计方法耗时的框架,使用的是 asm 进行方法插桩,使用也很简单,只需要给方法添加 @Cost 注解即可,插件会扫描类下面的所有方法是否有添加 @Cost 注解,如果有的话,则对方法进行插桩,插桩效果如下:


System.out.println("========start=========");
TimeUtil.setsStartTime("newFunc", System.nanoTime());
// 原方法执行体        
TimeUtil.setEndTime("newFunc", System.nanoTime());
System.out.println(TimeCache.getCostTime("newFunc"));
System.out.println("========end=========");
复制代码


但探究了一下源码,costTime 仅支持对当前 app module 有效,具体可以看 transfrom 的部分,对于 library 生成的 jar 部分是不做插桩的。

3、Matrix


Matrix 是腾讯的一款 APM 框架,在 matrix-gradle-plugin 模块中实现了对方法的插桩,具体原理可以参考我的文章《Matrix 之 TraceCanary 源码分析》。Matrix 并不会记录方法的名称,而是给每个插桩的方法生成唯一的 methodId,并且生成一份方法与 methodId 映射的配置文件,在做数据上报的时候,只需要上传 methodId 即可,云端只需要通过配置文件解析出 methodId 对应的方法名,即可查看到整个方法调用链,优点当然是不言而喻,数据大小和内存优化做的非常极致,缺点也有,methodId 会随着版本的变化而变化,需要维护每个版本的配置文件,在做数据分析时,需要根据版本号来调整。

4、Mamba


Mamba 的实现类似于 Matrix,但插桩的内容不是 methodId,而是当前的类、方法名和方法参数,插桩效果如下:


public void test() {
        long start = System.currentTimeMillis();
        Class<Test> cls = Test.class;
        Mamba.i(cls, "test");
    // 原方法体
        Mamba.i(cls, "test");
    }
复制代码


Mamba 本身不做逻辑,只将方法体的开始和结束交给实现 IMambaLoader 类来实现。Mamba 还提供了使用 @Track 注解来捕捉方法信息的功能,用于辅助无痕埋点方案参数值的获取功能,插桩效果如下:


//  原方法
@Track
private void open(String t, float a, double b, long c) {
        Toast.makeText(this, "What can I say?Mamba out", Toast.LENGTH_SHORT).show();
}
//  插桩之后效果
private void open(String str, float f, double d, long j) {
        Class<TrackActivity> cls = TrackActivity.class;
        Mamba.i(cls, "open", str, Float.valueOf(f), Double.valueOf(d), Long.valueOf(j));
        Toast.makeText(this, "What can I say?Mamba out", 0).show();
}
复制代码


缺点就是,基础类型需要装箱成引用类型,存储到 Object 数组中

Mamba 实现


Mamba 采用 gradle-plugin 和 asm 实现的方法插桩,Mamba 会遍历 full project 的 class,并利用 asm 在方法的开始和结束插入字节码。


Mamba 插入的字节码为什么是 Class、MethodName、Method Params 呢?

  • 插入 Class 的主要目的是为了更好的定位方法执行过程,由于各个类会存在相同方法名,会导致调用链不清晰
  • MethodName 是必要的,由于在插桩时就已记录好方法的名称,即使应用包被混淆,也能正常记录调用链
  • Method Params 的记录,主要是为了对方法更进一步的捕捉

细说 Method Params 的记录


在业务实践中,想要做到无痕埋点方案是不可能的,有的埋点部分会依赖上下文环境,并且还要记录当前的变量值,所以,我们不得不在业务代码中进行硬编埋点。

为了解决硬编问题,我想到的一个解决方案就是:将需要埋点的地方写成函数调用,然后将需要记录的变量作为函数的参数,然后给函数标记 @Track,然后 Mamba 会根据 @Track 注解自动去实现方法和参数的插桩,我们只需要在 Mamba 的实现类中进行埋点的数据即可。


下面给一份操作示例,需求是:在点击事件中记录 userName 变量


public class MyActivity{
    public void onClick(View view){
         String userName = editUserName.getText().toString();
         updateUser(userName);
         // 一般来说,我们可能会直接进行硬编,比如 TrackManager.get().logEvent("获取用户名称",userName)
    }
   /*
    * 更新用户名称
    */
    private void updateUser(String userName){
        ...
    }
}
复制代码


我们来改造一下:


public class MyActivity{
    public void onClick(View view){
         String userName = editUserName.getText().toString();
         updateUser(userName)
    }
    // 给更新用户添加一个 Track 注解即可
    @Track
    private void updateUser(String userName){
        ...
    }
}
复制代码


生成字节码后的结果为:


public class MyActivity{
    public void onClick(View view){
         String userName = editUserName.getText().toString();
         updateUser(userName)
    }
    @Track
    private void updateUser(String userName){
        Class<MyActivity> cls = MyActivity.class;
        Mamba.i(cls, "updateUser", userName);
        ...
    }
}
复制代码


我们只需要在 Mamba 的实现类中对 class 为 MyActivity,method 为 updateUser 的方法进行判断,并取出 params 值即可,例如:


override fun methodEnter(clazz: Class<*>?, methodName: String?, args: Array<out Any>?) {
        when (clazz) {
            MyActivity::class.java -> {
                  trackMyActivity(methodName, args)
            }
        }
  }
  private fun trackMyActivity(methodName: String?, args: Array<out Any>?){
        when(methodName){
            "updateUser"->{
              // 获取 userName 值
              val userName =  args!![0] as String
              // 使用 TrackManager 进行埋点操作
            }
        }
  }
复制代码


虽然 @Track 仍然需要在业务代码中进行编辑,但已经是尽量小的侵入业务代码,即使以后不需要记录用户名,我们也无需去删除 @Track 注解,只需要移除 Mamba 实现类中对 updateUser 的判断即可。


那么读者可能会问了,为啥你不直接做自动化收集方法参数,而是使用注解的方式侵入业务?其实,我也想过这种方案,但对于基础类型参数非常的不友好,如果我想统一收集方法参数,就必须使用一个大家都有的父类容器来存,所以,这里定义了 Object 数组来存储参数,但问题又来了,基础类型没有父类你怎么办,只能将基础类型包装成引用类型,也就是将 float 包装成 Float.valueOf() 存进 Object 数组,这种包装会消耗内存,试想,如果对所有的方法参数都进行包装收集,性能就成了问题。所以,这里采用 @Track 注解自己认为要收集的方法。

性能


大家可能会比较关心插桩后的性能问题,我这里列一下测试用例和结果:


1、方法插桩,多次测试耗时为 0 毫秒

image.png

2、方法参数插桩,多次测试,耗时大约在 2 毫秒


image.png

注意


  • 方法的参数收集目前只支持最多 5 个参数。
  • 插桩时还需要为 Mamba 实现类配置 exclude,避免插桩导致方法循环调用

总结


总的来说,各个方案实现都差不多,略微的差异在于业务的不同实现。

Mamba 也提供了两个默认的实现类:


  • CostTimeLoader : 统计方法耗时
  • TrackLoader : 捕捉方法信息
目录
相关文章
|
9月前
|
存储 算法
算法系列之搜索算法-广度优先搜索BFS
广度优先搜索(BFS)是一种非常强大的算法,特别适用于解决最短路径、层次遍历和连通性问题。在面试中,掌握BFS的基本实现和应用场景,能够帮助你高效解决许多与图或树相关的问题。
848 1
算法系列之搜索算法-广度优先搜索BFS
|
机器学习/深度学习 数据采集 算法
【阿旭机器学习实战】【35】员工离职率预测---决策树与随机森林预测
【阿旭机器学习实战】【35】员工离职率预测---决策树与随机森林预测
|
人工智能 JavaScript 程序员
Fitten Code:在VSCode插件市场备受欢迎的原因是什么?
随着AI技术的不断发展,AI在编写代码方面的能力也日益强大。充分利用AI的能力能够显著提高代码编写的效率和质量。今天我将向大家介绍一款备受瞩目的AI代码神器——Fitten Code,让我们一同揭开它神秘的面纱!
983 3
|
运维 监控 安全
构建高效运维体系:从监控到自动化的全面指南在当今数字化时代,运维作为保障系统稳定性和效率的重要环节,其重要性不言而喻。本文将深入探讨如何构建一个高效的运维体系,从监控系统的搭建到自动化运维的实施,旨在为读者提供一套完整的解决方案。
本文详细介绍了高效运维体系的构建过程,包括监控系统的选择与部署、日志分析的方法、性能优化的策略以及自动化运维工具的应用。通过对这些关键环节的深入剖析,帮助运维人员提升系统的可靠性和响应速度,降低人工干预成本,实现业务的快速发展和稳定运行。
|
存储 前端开发 Android开发
GB28181设备接入侧录像查询和录像下载技术探究之实时录像
我们在对接GB28181设备接入侧的时候,除了常规实时音视频按需上传外,还有个重要的功能,就是本地实时录像,录像后的数据,在执法记录仪等前端设备留底,然后,到工作站拷贝到专门的平台。
376 1
|
传感器 物联网 数据中心
探索ARM架构及其核心系列应用和优势
ARM架构因其高效、低功耗和灵活的设计,已成为现代电子设备的核心处理器选择。Cortex-A、Cortex-R和Cortex-M系列分别针对高性能计算、实时系统和低功耗嵌入式应用,满足了不同领域的需求。无论是智能手机、嵌入式控制系统,还是物联网设备,ARM架构都以其卓越的性能和灵活性在全球市场中占据了重要地位。
1281 1
|
机器学习/深度学习 JSON 数据库
Python每循环一次保存一次结果
Python每循环一次保存一次结果
430 1
|
数据采集 前端开发 搜索推荐
埋点tracker:前端数据埋点-方案设计思路梳理
埋点tracker:前端数据埋点-方案设计思路梳理
3442 0
小功能⭐️Unity解决物体移动速度过快不能检测到碰撞
小功能⭐️Unity解决物体移动速度过快不能检测到碰撞
|
Kubernetes 网络安全 容器
Cert Manager 申请 SSL 证书流程及相关概念 - 一
Cert Manager 申请 SSL 证书流程及相关概念 - 一