目前android技术最前沿莫属热修复与插件化的技术点,当下用得最多的就是阿里的Andfix,和微信的Tinker框架,针对源码的实现,再次做个记录。
我们从三方面来对于热修复和插件化进行分析。
class文件结构深入解析
class文件全名称为Java class文件,能被java虚拟机所识别的二进制文件。能够被jvm加载 并执行的文件格式。
记录一个类文件的所以信息
1.一种8位字节的二进制流文件
2.各个数据按顺序紧密的排列,无间隙(例如:cafebabe00000032....)
3.每个类和接口都对应一个class文件
注:其中u1,u2,u4,u8分别代表1字节,2字节,4字节和8字节的无符号类型整数。
我们编写一个java文件:
public class Hello{
private int test;
public int test(){
return test;
}
}
接下来用010Edit打开文件格式
ca fe ba be 00 00 00 32 00 12 0a 00 04 00 0e 09
00 03 00 0f 07 00 10 07 00 11 01 00 04 74 65 73
74 01 00 01 49 01 00 06 3c 69 6e 69 74 3e 01 00
03 28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69
6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 03
28 29 49 01 00 0a 53 6f 75 72 63 65 46 69 6c 65
01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 0c 00 07
00 08 0c 00 05 00 06 01 00 05 48 65 6c 6c 6f 01
00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65
63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05
00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09
00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01
b1 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00
01 00 01 00 05 00 0b 00 01 00 09 00 00 00 1d 00
01 00 01 00 00 00 05 2a b4 00 02 ac 00 00 00 01
00 0a 00 00 00 06 00 01 00 00 00 03 00 01 00 0c
00 00 00 02 00 0d
(ca fe ba be)
magic魔数,魔数的唯一作用是确定这个文件是否是class文件。魔数值固定为 0xCAFEBABE,不会改变。
(00 00 00 32)
minor_version、major_version副版本号和主版本号,当前副版本号是0,主版本号是50。
(00 12)
constant_pool_count常量池计数器,该常量数为17
constant_pool常量池,它包含 Class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。constant_pool 就下来就是分析这17个常量了。
常量池如下:
00 12 常量池的数目 18-1=17
0a 00 04 00 0e 方法:java.lang.Ojbect void ()
09 00 03 00 0f 方法 :Hello int test()
07 00 10 字符串:Hello
07 00 11 字符串:java.lang.Ojbect
01 00 04 74 65 73 74 字符串:test
01 00 01 49 字符串:I
01 00 06 3c 69 6e 69 74 3e 字符串:
01 00 03 28 29 56 字符串:()V
01 00 04 43 6f 64 65 字符串:Code
01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 字符串:LineNumberTable
01 00 03 28 29 49 字符串:()I
01 00 0a 53 6f 75 72 63 65 46 69 6c 65 字符串:SourceFile
01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 字符串:Hello.java
0c 00 07 00 08 NameAndType: ()V
0c 00 05 00 06 NameAndType:test I
01 00 05 48 65 6c 6c 6f 字符串:Hello
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 字符串: java/lang/Object
(00 21)
access_flags 描述的是当前类(或者接口)的访问修饰符, 如public, private等。
(00 03)
this_class类索引,this_class存的是当前类的名称在常量池里的索引,这里指向第三个常量,即是“Hello”。
(00 04)
super_class存的是父类的名称在常量池里的索引,这里指向第四个常量,即是“java/lang/Object”。
(00 00)
nterfaces包含interfaces_count和interfaces[]两个字段。这里interfaces_count为0,所以后面的内容也对应为空
fields_count字段计数器,fields_count 的值表示当前 Class 文件 fields数组的成员个数。
fields数组中的每个成员都必须是一个 fields_info 的数据项,用于表示当前类或接口中某个字段的完整描述。
methods_count 的值表示当前 Class 文件 methods[]数组的成员个数。
methods[]数组中的每个成员都必须是一个 method_info 的数据项,用于表示当前类或接口中某个方法的完整描述。
attributes_count 的值表示当前 Class 文件 attributes 表的成员个数。
attributes 表的每个项的值必须是 attribute_info 。
1.内存占用大,不适合移动端
2.堆栈的加载模式,加载速度慢。
3.文件io操作多,类查找慢。
能够被dvm识别,加载并执行的文件格式
通过ide自动帮我们build生成
手动通过dx命令去生成dex文件
手动运行dex文件运行在手机上
1.找到android sdk->build-tools->选择版本->dx,配置到环境变量中
2.在cmd中 dx --dex --output Hello.dex Hello.class
3.将dex文件push到手机存储卡中,然后执行adb shell进入手机控制台
4.使用dalvikvm -cp /sdcard/Hello.dex 类名 执行dex文件
记录整个工程中所有类文件的信息,记住是整个工程。
1.一种8位字节的二进制流文件
2.各个数据按顺序紧密排列,无间隙。
3.整个应用中所有java源文件都放在一个dex中
header是DEX文件头,包含magic字段、alder32校验值、SHA-1哈希值、string_ids的个数以及偏移地址等。DEX文件的头结构很固定,占用0x70个字节,具体定义代码如下所示(摘自DexFile.h):
struct DexHeader {
u1 magic[8]; /* includes version number */
u4 checksum; /* adler32 checksum */
u1 signature[kSHA1DigestLen]; /* SHA-1 hash */
u4 fileSize; /* length of entire file */
u4 headerSize; /* offset to start of next section */
u4 endianTag;
u4 linkSize;
u4 linkOff;
u4 mapOff;
u4 stringIdsSize;
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
};
magic[8]:共8个字节。目前为固定值dex\n035。
checksum:文件校验码,使用alder32算法校验文件除去magic、checksum外余下的所有文件区域,用于检查文件错误。
signature:使用 SHA-1算法hash除去magic,checksum和signature外余下的所有文件区域 ,用于唯一识别本文件 。
fileSize:DEX文件的长度。
headerSize:header大小,一般固定为0x70字节。
endianTag:指定了DEX运行环境的cpu字节序,预设值ENDIAN_CONSTANT等于0x12345678,表示默认采用Little-Endian字节序。
linkSize和linkOff:指定链接段的大小与文件偏移,大多数情况下它们的值都为0。link_size:LinkSection大小,如果为0则表示该DEX文件不是静态链接。link_off用来表示LinkSection距离DEX头的偏移地址,如果LinkSize为0,此值也会为0。
mapOff:DexMapList结构的文件偏移。
stringIdsSize和stringIdsOff:DexStringId结构的数据段大小与文件偏移。
typeIdsSize和typeIdsOff:DexTypeId结构的数据段大小与文件偏移。
protoIdsSize和protoIdsSize:DexProtoId结构的数据段大小与文件偏移。
fieldIdsSize和fieldIdsSize:DexFieldId结构的数据段大小与文件偏移。
methodIdsSize和methodIdsSize:DexMethodId结构的数据段大小与文件偏移。
classDefsSize和classDefsOff:DexClassDef结构的数据段大小与文件偏移。
dataSize和dataOff:数据段的大小与文件偏移。
1.本质上他们都是一样的,dex是从class文件演变而来的
2.class文件存在许多冗余信息,dex会去除冗余信息