理解一个工具的最快方式就是跑起来,然后原理自然了然于心 

本文以一个最简单的demo来实现对ASM全过程的了解。创建一个Child类,有一个call方法,最终的目的是在class类的call方法下增加一行输出语句。

ASM概念,操作流程:

需要创建一个 ClassReader 对象,将 .class 文件的内容读入到一个字节数组中

然后需要一个 ClassWriter 的对象将操作之后的字节码的字节数组回写

需要事件过滤器 ClassVisitor。在调用 ClassVisitor 的某些方法时会产生一个新的 XXXVisitor 对象,当我们需要修改对应的内容时只要实现自己的 XXXVisitor 并返回就可以了

1.引入最新的依赖:

//ASM相关依赖
implementation 'org.ow2.asm:asm:9.2'
implementation 'org.ow2.asm:asm-commons:9.1'

2.创建Child类,有一个phone属性,以及call()方法

public class Child {public String phone;public void call() {System.out.println("call");}
}

最终目的是在方法下面加一句,输入手机的名字和价格:

System.out.println("use :" + this.phone + " with price :" + this.price + " call");

3.创建方法过滤器ChildMethod,这里没有用MethodVisitor,使用了AdviceAdapter,因为它是 MethodVisitor 的子类,功能更全。

public class ChildMethod extends AdviceAdapter implements Opcodes {public ChildMethod(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {super(api, methodVisitor, access, name, descriptor);}@Overridepublic void visitCode() {//表示 ASM 开始扫描这个方法super.visitCode();}@Overridepublic void visitEnd() {//表示方法扫码完毕super.visitEnd();}@Overrideprotected void onMethodEnter() {//进入这个方法super.onMethodEnter();}@Overrideprotected void onMethodExit(int opcode) {//即将从这个方法出去super.onMethodExit(opcode);Label label1 = new Label();mv.visitLabel(label1);mv.visitLineNumber(16, label1);mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitTypeInsn(NEW, "java/lang/StringBuilder");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);mv.visitLdcInsn("use :");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "com/ng/ngstatistical/test/asmhook/Child", "phone", "Ljava/lang/String;");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(" with price :");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "com/ng/ngstatistical/test/asmhook/Child", "price", "Ljava/lang/String;");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(" call");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);}@Overridepublic void visitInsn(int opcode) {//扫描Opcodes操作符super.visitInsn(opcode);}
}

可以看到,添加小小的一行源代码却需要我们编写这么多的字节码,其实这里有一个取巧的办法,就是先在Child.java类中输入代码,实现我们想要的效果,再使用ASM Bytecode Viewer 插件来看生成的字节码,如下图所示:

之后,再复制到我们的方法过滤器的指定位置就好了~如此简单~高效~快捷~

4.然后需要实现类过滤器,在类过滤器中按方法名过滤方法,然后调用我们刚刚实现的方法过滤器:

public class ChildClassVisitor extends ClassVisitor {/*** @param api asm的api版本*/public ChildClassVisitor(int api) {super(api);}public ChildClassVisitor(int api, ClassVisitor classVisitor) {super(api, classVisitor);}@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);if (name.equals("call")) {return new ChildMethod(Opcodes.ASM9,methodVisitor,access,name,descriptor);}return methodVisitor;}
}

5.最后需要新创建一个类,编写main函数,首先需要调用一下Child类的call方法来生成class文件:

//生成class
private static void testChild() {Child child = new Child();child.call();
}public static void main(String[] args) {testChild();//startHook();}

运行之后,可以看到生成的class:

6.最后在main函数执行asm的调度方法:

//Child 的 class文件路径public static final String LOCAL_PATH = "/Users/xiaoguagua/AndroidProjects/MyProjects/ng_projects/NgStatistical/app/build/intermediates/javac/debug/classes/com/ng/ngstatistical/test/asmhook";private static void startHook() {try {//1.首先创建ClassReader,读取目标类Child的内容ClassReader cr = new ClassReader(Child.class.getName());//2.然后创建ClassWriter对象,ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);ClassVisitor cv = new ChildClassVisitor(ASM9, cw);cr.accept(cv, Opcodes.ASM9);// 获取生成的class文件对应的二进制流byte[] code = cw.toByteArray();//将二进制流写到out/下FileOutputStream fos = new FileOutputStream(LOCAL_PATH + "/Child.class");fos.write(code);fos.close();} catch (Exception e) {e.printStackTrace();}}

运行,再去看刚才的Child.class文件,发现,它已经被改掉了,成功~

Android——ASM 极速上手 简单使用相关推荐

  1. 万师傅使用云产品,上手简单、开箱即用、省去运维烦恼

    云栖号案例库:[点击查看更多上云案例] 不知道怎么上云?看云栖号案例库,了解不同行业不同发展阶段的上云方案,助力你上云决策! 整体架构 每当我在思考技术选型方案的时候,翻翻阿里云的官网,总能找到我想要 ...

  2. android扫码 超简单零代码

    android扫码 超简单零代码 小序 背景介绍 前期准备 zxing和华为扫码服务对比 开始搬运 结语 小序 这是一篇纯新手教学,本人之前没有任何安卓开发经验(尴尬),本文也不涉及任何代码就可以使用 ...

  3. 小米5android p,久违的刷机 小米MIX Android P DP5 上手体验

    久违的刷机 小米MIX Android P DP5 上手体验 2018-07-30 20:15:44 29点赞 60收藏 34评论 前言 随着国产定制安卓系统越来越完善,刷机正在从安卓机的主要特征中逐 ...

  4. 逮虾户!Android程序调试竟简单如斯

    逮虾户!Android程序调试竟简单如斯 PS:行吧,不用百度了,逮虾户是<头文字D>的一首配乐<Deja vu>,中文谐音 "逮虾户",飙车漂移专用BGM ...

  5. android 如何加固,Android应用加固的简单实现方案(二)

    Android应用加固的简单实现方案(二) 前言 上一篇文章介绍了基于dex加固方案的两种具体实现.相对于手动加固,基于gradle实现的加固方案效率有了进一步提升.但是,还是需要在壳Module中增 ...

  6. 《Android App开发入门:使用Android Studio 2.X开发环境》——1-3 Android Studio 快速上手...

    1-3 Android Studio 快速上手

  7. Android TabLayout(选项卡布局)简单用法实例分析

    本文实例讲述了Android TabLayout(选项卡布局)简单用法.分享给大家供大家参考,具体如下: 我们在应用viewpager的时候,经常会使用TabPageIndicator来与其配合.达到 ...

  8. android 共享数据,android进程间共享简单数据

    我们知道,在android中,保存简单的数据最方便的就是使用SharedPreferences,然而,SharedPreferences虽然说也可以设置成进程间共享数据,但是并不可靠(更致命的是,不同 ...

  9. Android PC投屏简单尝试—最终章2

    源码地址:https://github.com/deepsadness/AppRemote 上一章中,我们简单实现了PC的投屏功能. 但是还是存在这一些缺陷. 屏幕的尺寸数据是写死的 不能通过PC来对 ...

  10. Android PC投屏简单尝试—最终章1

    回顾之前的几遍文章,我们分别通过RMTP协议和简单的Socket 发送Bitmap图片的Base64编码来完成投屏. 回想这系列文章的想法来源-Vysor,它通过 USB来进行连接的.又看到了 scr ...

最新文章

  1. 微信小程序时间戳转化为时间
  2. Linux学习笔记之文件管理和目录管理类命令
  3. UICollectionView自定义布局(二)
  4. 通过电机编码器AB相输出确定电机转向
  5. 在Windows 64位下为PHP5.6.14安装redis扩展
  6. 制作openstack Centos镜像 -- Example: CentOS image
  7. centos 忘记 root 密码
  8. 全国计算机等级考试题库二级C操作题100套(第16套)
  9. C语言试题六十八之请编写函数实现亲密数
  10. numpy.linspace()的使用方法
  11. 基于django快速开发一个网站(一)
  12. [android]实现拖动效果
  13. linux从入门到精通(第2版)pdf
  14. PostgreSQL修炼之道之PostgreSQL安装与配置(二)
  15. 自愈的三把钥匙:接受,改变,离开
  16. R语言编写自定义函数计算分类模型评估指标:准确度、特异度、敏感度、PPV、NPV、数据数据为模型预测后的混淆矩阵、比较多个分类模型分类性能(逻辑回归、决策树、随机森林、支持向量机)
  17. matlab中m_map工具箱绘制大圆航线
  18. 手机已连接但无法访问互联网,碰到这个情况怎么破?想不到是这样
  19. 如何将视频中的音频提取出来?
  20. 【ArcGIS自定义脚本工具】利用聚合方法批量生成分辨率降低版本的栅格

热门文章

  1. 适配器模式(Adapter模式)详解
  2. 码织匠C语言程序设计,“C语言程序设计”课程的教学资源融合建设研究
  3. SketchUp Pro 2021 v21.0.391 草图大师安装说明
  4. 计算机快捷方式app卸载,一打开电脑就自动出现的快捷方式软件删不掉怎么办
  5. Consul删除服务
  6. 成为java架构师需要几年,详细说明
  7. PHP IE下载时提示”无法复制 无法读取源文件或磁盘”的解决办法
  8. JAVA小项目--商品管理系统
  9. java图书馆_java入门第三季--图书馆借书系统
  10. 控制台程序不显示dos窗口的方法