Android 开发高手课
张绍文
前微信高级工程师,Tinker 负责人
52722 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 62 讲
导读 (1讲)
模块一 高质量开发 (25讲)
Android 开发高手课
15
15
1.0x
00:00/00:00
登录|注册

27 | 编译插桩的三种方法:AspectJ、ASM、ReDex

上手难
操作灵活
劣势
使用简单
成熟稳定
代码分析
代码修改
代码监控
代码生成
ReDex
ASM
AspectJ
字节码
编译插桩的应用场景
课后作业
总结
编译插桩的三种方法
编译插桩的基础知识
编译插桩都有哪些方法?

该思维导图由 AI 生成,仅供参考

只要简单回顾一下前面课程的内容你就会发现,在启动耗时分析、网络监控、耗电监控中已经不止一次用到编译插桩的技术了。那什么是编译插桩呢?顾名思义,所谓的编译插桩就是在代码编译期间修改已有的代码或者生成新代码。
如上图所示,请你回忆一下 Java 代码的编译流程,思考一下插桩究竟是在编译流程中的哪一步工作?除了我们之前使用的一些场景,它还有哪些常见的应用场景?在实际工作中,我们应该怎样更好地使用它?现在都有哪些常用的编译插桩方法?今天我们一起来解决这些问题。

编译插桩的基础知识

不知道你有没有注意到,在编译期间修改和生成代码其实是很常见的行为,无论是 Dagger、ButterKnife 这些 APT(Annotation Processing Tool)注解生成框架,还是新兴的 Kotlin 语言编译器,它们都用到了编译插桩的技术。
下面我们一起来看看还有哪些场景会用到编译插桩技术。
1. 编译插桩的应用场景
编译插桩技术非常有趣,同样也很有价值,掌握它之后,可以完成一些其他技术很难实现或无法完成的任务。学会这项技术以后,我们就可以随心所欲地操控代码,满足不同场景的需求。
代码生成。除了 Dagger、ButterKnife 这些常用的注解生成框架,Protocol Buffers、数据库 ORM 框架也都会在编译过程生成代码。代码生成隔离了复杂的内部实现,让开发更加简单高效,而且也减少了手工重复的劳动量,降低了出错的可能性。
代码监控。除了网络监控和耗电监控,我们可以利用编译插桩技术实现各种各样的性能监控。为什么不直接在源码中实现监控功能呢?首先我们不一定有第三方 SDK 的源码,其次某些调用点可能会非常分散,例如想监控代码中所有 new Thread() 调用,通过源码的方式并不那么容易实现。
代码修改。我们在这个场景拥有无限的发挥空间,例如某些第三方 SDK 库没有源码,我们可以给它内部的一个崩溃函数增加 try catch,或者说替换它的图片库等。我们也可以通过代码修改实现无痕埋点,就像网易的HubbleData、51 信用卡的埋点实践
代码分析。上一期我讲到持续集成,里面的自定义代码检查就可以使用编译插桩技术实现。例如检查代码中的 new Thread() 调用、检查代码中的一些敏感权限使用等。事实上,Findbugs 这些第三方的代码检查工具也同样使用的是编译插桩技术实现。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

编译插桩技术在代码编译期间对已有代码进行修改或生成新代码,在启动耗时分析、网络监控、耗电监控等场景发挥重要作用。应用场景包括代码生成、监控、修改和分析,可分为Java文件和字节码两种方式,其中字节码操作功能更强大。常用的Java字节码处理框架有AspectJ和ASM,AspectJ功能强大但性能较低,而ASM操作灵活但上手难度较大。此外,ReDex不仅是一款Dex优化工具,还提供了Method Tracing和Block Tracing工具。总的来说,编译插桩技术在性能优化和代码分析中具有重要作用,而不同的框架适用于不同的场景。读者可根据需求选择合适的工具。 Dalvik字节码的处理相比Java字节码更复杂,直接操作Dalvik字节码的工具并不多。市面上一些修改Dalvik字节码的库包括Xposed、Smali、dexlib等。文章介绍了几种代表性的框架,强调编译插桩技术的广泛应用和重要性,同时提醒读者根据需求选择合适的工具。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Android 开发高手课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(20)

  • 最新
  • 精选
  • Eateeer
    看到这篇文章表示非常激动,这段时间自己也在尝试了解和使用编译插桩的相关技术,编译插桩涉及的东西很多,特别是 ASM 与 Transform 结合后产生的一些列化学反应,比如无埋点、增量编译、Instant Run 等,感觉像是打开了一个新世界的大门。 给大家安利一个 IntelliJ 插件 - ASM Bytecode Outline,可以用来帮助编写字节码。 再次感谢绍文大大的精彩文章!

    作者回复: 感谢分享,这个工具挺有用的,有时候我也会用

    2019-02-21
    23
  • su
    这篇含金量是目前整个专栏最好的,谢谢

    作者回复: 需要课后补充知识以及实践

    2019-02-22
    5
  • splm
    嗯很不错啊。之前一直对插桩只有印象,但具体做什么还是不了解,但今天看了这篇文章,受益匪浅。原来插桩的技术,自己之前就用过了。 这个是自己之前利用APT和JavaPoet写的一个开发工具,大家可以交流探讨一下。 https://github.com/splm/WeBase 也希望大家能多点几个星。

    作者回复: 非常好,赞赞赞

    2019-03-04
    2
  • seven
    文哥牛逼!这篇至少要看个十几遍~玩溜asm估计要练习个几个月了

    作者回复: Asm还是挺多用途的

    2019-02-21
    1
  • itismelzp
    非常棒!但是我有个问题,就是asm插桩后的class行号就变了吧,这样堆栈信息就对不上了。。。

    作者回复: 行号信息也是可以处理的

    2019-07-15
    3
  • 孫小逗
    请问,AS3.1.3,安装ASM Bytecode Outline后,没有显示字节码是什么情况?

    作者回复: 没有显示字节码指的是?

    2019-03-19
    2
  • X
    请问Systrace在Windows上是不是不支持,我试过在Mac上可以用的,但是Windows上就报错,Google了下好像很多人反馈这个但是没有解决方案!

    作者回复: Systrace应该是支持的,但是我也好久没使用过Windows了

    2019-03-16
  • LD
    另外一个字节码处理工具javaasist也不错哦 使用比asm简单,达到的效果和asm一致(直接插入代码,不像aspectj需要生成包装函数)

    作者回复: javassit效率比较低

    2019-02-21
  • 王大大
    推荐一个简单轻量的asm框架lancet,https://github.com/eleme/lancet,通过这个库实现业务代码的动态监测
    2019-03-28
    8
  • l晟睿致远
    这两天扣了一下asm操作字节码,把onMethodExit方法改了一下,如果time大于设定的时间就使用error级别打印。做为练习发出来共勉。内容较长分三段发,一、 protected void onMethodExit(int opcode) { int stringBuilderIndex = newLocal(Type.getType("java/lang/StringBuilder")); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitVarInsn(LLOAD, timeLocalIndex);//把变量压入栈 mv.visitInsn(LSUB);//执行long 类型减法 上面的减下面 ,此处的值在栈顶 mv.visitVarInsn(LSTORE, timeLocalIndex);//因为后面要用到这个值所以先将其保存到本地变量表中 mv.visitVarInsn(LLOAD,timeLocalIndex);//压入栈 mv.visitLdcInsn(100L);//压入100入栈 作为比较,如果执行时间大于100毫秒使用ERROR打印 否使用debug mv.visitInsn(LCMP);//执行long类型比较指令 Label l1 = new Label(); mv.visitJumpInsn(Opcodes.IFLE,l1); //弹出上面压入的两个long 比较,此时栈空,到label1 //if 比较成立执行 mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false); mv.visitVarInsn(Opcodes.ASTORE, stringBuilderIndex);//需要将栈顶的 stringbuilder 保存起来否则后面找不到了 mv.visitVarInsn(Opcodes.ALOAD, stringBuilderIndex); mv.visitLdcInsn(className + "." + methodName + " time:"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitInsn(Opcodes.POP); mv.visitVarInsn(Opcodes.ALOAD, stringBuilderIndex); mv.visitVarInsn(Opcodes.LLOAD, timeLocalIndex); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false); mv.visitInsn(Opcodes.POP);
    2020-05-14
    3
收起评论
显示
设置
留言
20
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部