当前位置: 首页 > news >正文

Android编译插桩ASM技术探究(一)

1,概念介绍

        先搞明白什么是编译插桩。我们写的Java/Kotlin代码,要经过一系列加工才能变成机器上能跑的APK,apk文件编译过程如下,

Java代码 → javac编译 → Class文件 → 打包成Dex → 生成APK

编译插桩就像在"Class文件"和"Dex"之间加了个质检员,它会:

  1. 拦下所有Class文件
  2. 按你的要求修改(比如加日志、加统计)
  3. 再覆盖重新生成Dex

插桩技术优点:

  • 不用改源码,业务代码干干净净
  • 统一处理,避免漏改、错改
  • 一次开发,全项目生效

2,ASM

要修改Class文件,就得懂字节码。但字节码这东西,人类看了脑壳疼(不信你看下面这段):

public void test() {0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: return
}

这时候ASM就登场了。它是一个操作字节码的框架,能帮你:

  • 读懂Class文件(不用自己解析字节码)
  • 修改Class文件(不用记那些鬼画符一样的指令)
  • 生成新的Class文件(徒手撸字节码什么的,不存在的)

3,ASM开发步骤

3.1 搭建插桩环境

插桩通常通过Gradle插件实现,步骤如下:

  1. 创建一个Android Library模块(比如叫asm-plugin)
  2. 在build.gradle里引入必要依赖:

   //gradle sdkimplementation gradleApi()//groovy sdkimplementation localGroovy()   implementation 'org.ow2.asm:asm:5.0.3'implementation 'org.ow2.asm:asm-commons:5.0.3'

3.2 自定义Transform

Transform是Android Gradle提供的用于处理Class文件的接口,我们的插桩逻辑就放在这里。

先定义一个Transform:

public class ASMTransform extends Transform {// 给Transform起个名字@Overridepublic String getName() {return "ASMTransform";}// 告诉Gradle我们要处理哪些类型的文件@Overridepublic Set<QualifiedContent.ContentType> getInputTypes() {return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES);}// 告诉Gradle我们要处理哪些范围的文件@Overridepublic Set<QualifiedContent.Scope> getScopes() {return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT, QualifiedContent.Scope.EXTERNAL_LIBRARIES);}// 是否支持增量编译(提升编译速度)@Overridepublic boolean isIncremental() {return true;}// 核心方法:处理Class文件@Overridepublic void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {// 1. 遍历所有输入的Class文件invocation.getInputs().forEach(input -> {// 处理项目自身的Classinput.getDirectoryInputs().forEach(dirInput -> {processDir(dirInput.getFile());// 把处理后的文件输出到下一个流程File dest = invocation.getOutputProvider().getContentLocation(dirInput.getName(), dirInput.getContentTypes(), dirInput.getScopes(), Format.DIRECTORY);FileUtils.copyDirectory(dirInput.getFile(), dest);});// 处理第三方库的Class(Jar包)input.getJarInputs().forEach(jarInput -> {processJar(jarInput.getFile());// 输出处理后的JarFile dest = invocation.getOutputProvider().getContentLocation(jarInput.getName(), jarInput.getContentTypes(),jarInput.getScopes(), Format.JAR);FileUtils.copyFile(jarInput.getFile(), dest);});});}// 处理目录中的Class文件private void processDir(File dir) {if (dir.isDirectory()) {for (File file : dir.listFiles()) {if (file.isDirectory()) {processDir(file);} else if (file.getName().endsWith(".class")) {// 用ASM处理单个Class文件modifyClass(file);}}}}// 处理Jar包中的Class文件(略)private void processJar(File jarFile) { ... }
}

3.3 给所有方法加耗时统计

        现在到了最关键的部分:用ASM修改Class文件。我们的目标是——给所有方法前后加上耗时统计,就像这样:

// 原方法
public void login(String username) {// 登录逻辑
}// 插桩后
public void login(String username) {long start = System.currentTimeMillis();// 登录逻辑long end = System.currentTimeMillis();Log.d("耗时", "login: " + (end - start) + "ms");
}

实现这个需求的ASM代码:

private void modifyClass(File classFile) {try {// 1. 读取原Class文件ClassReader cr = new ClassReader(Files.readAllBytes(classFile.toPath()));// 2. 准备写入修改后的Class文件ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);// 3. 自定义ClassVisitor处理类ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {// 当访问到方法时回调@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {// 获取原始方法的MethodVisitorMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);// 过滤掉构造方法和静态代码块if (name.equals("<init>") || name.equals("<clinit>")) {return mv;}// 返回自定义的MethodVisitor,用于修改方法return new MethodVisitor(Opcodes.ASM9, mv) {// 方法开始时调用(Code指令前)@Overridepublic void visitCode() {// 插入: long start = System.currentTimeMillis();mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);mv.visitVarInsn(Opcodes.LSTORE, 1); // 存储到局部变量1super.visitCode(); // 执行原方法的Code指令}// 方法结束时调用(return指令前)@Overridepublic void visitInsn(int opcode) {// 只在返回指令前插入代码if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {// 插入: long end = System.currentTimeMillis();mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);mv.visitVarInsn(Opcodes.LSTORE, 3); // 存储到局部变量3// 插入: Log.d("耗时", "方法名: " + (end - start) + "ms");mv.visitLdcInsn("耗时"); // 日志标签// 拼接字符串:"方法名: " + (end - start) + "ms"mv.visitLdcInsn(name + ": ");mv.visitVarInsn(Opcodes.LLOAD, 3); // 加载endmv.visitVarInsn(Opcodes.LLOAD, 1); // 加载startmv.visitInsn(Opcodes.LSUB); // end - startmv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "toString", "(J)Ljava/lang/String;", false);mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", false);mv.visitLdcInsn("ms");mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", false);// 调用Log.dmv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false);mv.visitInsn(Opcodes.POP); // 消费返回值}super.visitInsn(opcode); // 执行原返回指令}};}};// 开始处理Class文件cr.accept(cv, ClassReader.EXPAND_FRAMES);// 4. 把修改后的字节码写回文件Files.write(classFile.toPath(), cw.toByteArray());} catch (Exception e) {e.printStackTrace();}
}

3.4  注册插件,让插桩生效

最后一步,把我们的Transform注册到Gradle插件中:

public class ASMPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) {// 获取Android扩展AppExtension android = project.getExtensions().getByType(AppExtension.class);// 注册我们的Transformandroid.registerTransform(new ASMTransform());}
}

在resources/META-INF/gradle-plugins/asmplugin.properties中声明插件:

implementation-class=com.example.ASMPlugin

然后在app模块的build.gradle中应用插件:

plugins {id 'com.android.application'id 'asmplugin' // 应用我们的插件
}

这样,每次编译时,ASM就会自动给所有方法加上耗时统计了!

http://www.dtcms.com/a/471253.html

相关文章:

  • 西安网站建设软件模板下载失败
  • 学校的网站开发过程钓鱼软件生成器
  • 宁波企业建站网站建设科技
  • 网站开发学习什么网站建设实施规范
  • 安徽建设厅网站进不去郑州网站优化公司平台
  • 如何在网站投放广告wordpress标题后乱码
  • 【C++学习】继承和多态
  • 开发一个网站需要多少人杭州品牌vi设计公司
  • 韩雪冬做网站多少钱网址搜索ip地址
  • Google 智能体设计模式:探索与发现
  • 湛江购房网官方网站沈阳点金网站建设
  • 靖江网站设计做网站服务好
  • 制作网站的素材wordpress怎么改表缀
  • 合肥网站制作公司有哪些公司网站维护中 源码
  • C++的string类
  • 【软件设计师中级】计算机组成与结构(五):指令系统与计算机体系结构 - CPU的“思维语言“与架构蓝图
  • 柳州网站建设数公式大全wordpress 输出the id
  • 17网站一起做网店杭州wordpress 当前页面
  • 百度推广客户端兰州网站seo收费
  • 建站系统主要包括什么自适应网站导航怎么做
  • 服务器运维(五)服务器漏洞扫描赛博修仙版本——东方仙化神期
  • 深圳建专业网站上海网站关键词优化服务
  • c语言-运算符
  • 从AAAI2025中挑选出对目标检测有帮助的文献——第一期
  • Flowise与cpolar:AI工作流的无界运行解决方案
  • 做网站的三个软件兼职做视频的网站
  • 营销型网站的好处企业网站 生成html
  • AI学习日记——卷积神经网络(CNN):完整实现与可视化分析
  • 【开题答辩全过程】以 报修系统为例,包含答辩的问题和答案
  • 中国做木线条的网站北京高档网站建设