先看一个例子:
public class Demo{
{
i = 2;
}
private int i = 1;
private static int j = 1;
static{
j = 2;
}
}
编译器处理可以理解下面的情形:
public class Demo{
//类初始化调用,只会调用一次
void static constructor <clinit>(){
j = 1;
j = 2;
}
//构造函数
void constructor <init>(){
super.<init>();
i = 2;
i = 1;
}
}
静态field初始化,静态代码块
热部署除了不支持method/field新增,同时也不支持<clinit>
(classinit简称)这个方法修复,因为这个方法不是Java中方法,而是编译器自动添加的,静态field和静态代码块会被编译器编译到<clinit>
方法中,他们在<clinit>
方法中的先后顺序就是在源码中出现的顺序。
类加载进行类初始化的时候会调用clinit方法,一个类只会调用一次,一下情况都会尝试加载一个新类:
- new 一个类的对象(new-instance指令)
- 调用类的静态方法(invoke-static 指令)
- 获取类的静态field (sget 指令)
如果一个类没有被加载,会走dvmResolveClass->dvmLinkClass->dvmInitClass
,dvmInitClass
会尝试对父类进行初始化,之后调用本类的·<clinit>
方法,使得静态成员和静态代码块得以初始化。
非静态field初始化,非静态代码块
上面的实例可以看到非静态field、非静态代码块会被编译到<init>
默认无参构造函数方法中,非静态field、非静态代码块在init中初始化顺序和在源码中顺序一致。
构造方法会被Android编译器自动翻译成<init>方法
如果是有参构造函数,每个有参构造函数都会执行一个非静态域的初始化和一个非静态代码块。
热部署解决方案
<clinit>
会在Dalvik虚拟机加载类进行类初始化调用,所以任何静态field和静态代码块的变更都会翻译到<clinit>
中导致热部署失败。(也就说这个方法是自动添加的,而且只会执行一次,因此补丁类的静态field和静态代码块不会再有初始化的机会)
静态field初始化、静态代码块会被编译到<init>
方法中,这个可以视为一个普通方法的变更,因此静态field初始化、静态代码块支持热部署。
来自《深入探索Android热修复原理》