Android进阶解密⑤—热修复

在此之前已经总结过ClassLoader的原理,以及通过ClassLoader方式实现的热修复思路,实现热修复的方法有很多,大致有三种方式:

  • ClassLoader
  • Instant Run(ASM字节码插装)
  • 底层替换方案

本文重点介绍后两种实现热修复的方式, 第一种方式可以参考这篇文章:ClassLoader&双亲委派模型

Instant Run方案(ASM字节码插装)

关于Instant Run的了解可以参考这篇文章: Android Studio新功能解析,你真的了解Instant Run吗?
Instant Run的原理是在第一次构建apk的时候使用ASM每一个方法中注入一段代码,这段代码的作用是判断这个方法是否发生改变,如果没有改变就什么都不做,如果发生了改变,就会生成一个替换类代替

ASM:

ASM 是一个java字节码修改框架,可以动态的生成类或者修改现有类的功能,可以直接创建class字节码或者在虚拟机执行之前改变现有类的行为;

插桩的作用:

代码插入和代码替换,常见的使用场景有热修复,埋点,性能检测

ASM框架的依赖:
    // ASM 相关依赖
    implementation 'org.ow2.asm:asm:7.1'
    implementation 'org.ow2.asm:asm-commons:7.1'

回忆一下安卓打包流程,其中有一个步骤就是将class字节码通过dx工具转为dex文件,在转化前,我们可以通过ASM对字节码进行修改;

使用ASM进行插桩之前需要了解的知识点:
  • Transform API:android在将class转成dex之前给我们预留的一个接口
  • Gradle自定义插件
整体思路

使用Transform API在字节码转为dex前通过ASM提供的API对字节码进行修改

ASM框架的核心API介绍:

  • ClassReader:字节码读取类
  • Visitor:调用ClassReader的accept()需要传入Visitor,常见的Visitor有ClassVisitor,MethodVisitor,当类或者方法被读取到时会触发Visitor中对应的事件;
    void asm() throws Exception {
        FileInputStream fileInputStream = new FileInputStream(new File("字节码class"));
        // 字节码分析器
        ClassReader clazzReader = new ClassReader(fileInputStream);
        // 分析字节码
        clazzReader.accept(new MyVisitor(Opcodes.ASM7),ClassReader.EXPAND_FRAMES);
    }

创建一个ClassReader并且调用它的accept(),传入一个visitor(ClassVisitor),用于监听字节码的读取;

MyVisitor
    static class MyVisitor extends ClassVisitor{
        public MyVisitor(int api) {
            super(api);
        }
        //  当读取到类的方法时,会回调这个方法
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
            return new MyMethodVisitor(api,methodVisitor,access,name,descriptor);
        }
    }

在我们自定义的ClassVisitor类中的visitMethod(),当字节码中Method相关的字节码被读取的时候会回调这个方法,在这个方法中,需要返回一个MethodVisitor对象,我们可以自定义一个MethodVisitor来对Method相关的字节码进行修改:

MyMethodVisitor:
    static class MyMethodVisitor extends AdviceAdapter {  // AdviceAdapter是MethodVisitor的子类

        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }

        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
        }

        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode);
        }
    }

AdviceAdapter是MethodVisitor的子类,它提供了更多的API让我们对字节码进行修改,在此,重写了两个方法,从名称就可以看到这个方法的作用:在这个Method执行前和执行后调用;

在onMethodEnter/Exit中添加插桩的操作:这里的所有操作都是通过ASM提供的方法进行操作,这些方法是跟字节码指令一一对应的,所以需要参照字节码指令来写,可以通过AS的插件ASM Bytecode Viewer查看字节码
1.我需要插入这样的代码:
        System.out.println("Hello");
2.通过ASM Bytecode Viewer查看到的字节码:
   L0
    LINENUMBER 12 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "Hello"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 13 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/leap/latte/asm/Hello; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
3. 在onMethodEnter()插桩的写法:
        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            getStatic(Type.getType("Ljava/lang/System;"),"out",Type.getType("Ljava/io/PrintStream;"));
            visitLdcInsn("Hello");
            invokeVirtual(Type.getType("Ljava/io/PrintStream;"),new Method("println","(Ljava/lang/String;)V"));
        }

首先来看字节码,再看插桩写法:

  • GETSTATIC指令:获取一个类的静态成员 —对应—getStatic(),需要传入这个类的Type,这个static成员的名称,这个成员的type(这里需要使用签名类型)
  • LDC指令:向栈帧中压入Hello字符串
  • INVOKEVIRTUAL指令:执行这个对象的方法 —对应— invokeVirtual() ,需要传入这个对象的类的Type,执行方法的Method(这是asm包下的method,不是反射包下的)

每一个java代码再编译后,都会得到多行字节码,这些字节码描述了底层栈帧操作的行为,在使用asm进行插桩的时候,asm对字节码操作栈帧的步骤一一的进行了封装,所以我们可以通过asm对字节码进行修改;

上面的例子只做简单的参考,具体asm相关的api可以参考 ASM官网

配合trainsform接口在dx转换前修改class字节码

底层替换方案:

底层替换方案会直接在native层修改原有的类,每一个java方法都会在native层有一个对应的ArtMethod指针,这个指针包含了Java方法的所有信息:方法执行的入口,访问权限,执行地址等;

ArtMethod:

修改ArtMethod结构体的某个字段或者替换整个结构体,这就是底层替换方案,采用底层方案主要是阿里系:AndFix,Dexposed,阿里百川;

你可能感兴趣的:(Android进阶解密⑤—热修复)