class文件与dex文件解析
关于Android的热修复与插件化技术在如今基本上已经成为了“时髦技术”的标配了,或者说用来进行“炫技”的一种方式,毕境如今Android已经发展得非常之成熟了,基本上APP用的到东东都差不多,除了业务不同之外,但是!对于热修复与插件化并不是每个公司或者每个程序员愿意去应用到商用项目上的,因为既使不加它貌似对传统APP的开发也木有啥影响,毕境加它还是有些繁锁的,而不管有没有在商用APP上去集成过它们,并不影响它被众多开发者所追捧,如今去面个试我想被问到热修复与插件化相关的技术问题应该只多不少,而且还得让你去对它底层的原理进行一些阐述,如果不需要面试也得有必要去掌握这一技术,因为毕境能集成到自己的APP上是能够实际解决APP的一些问题的,基于此有必要系统的去探究,彻底掌握它们,所以接下来会从基础开始一点点去揭开它神秘的面纱。
对于Dex文件是能够在Android被执行的一种格式,而它是由class文件进行演变过来的,在android的热修复中是需要涉及到Dex文件之间的一个diff操作的,而它的基础就是得对其文件结构有一定的了解,所以这里基础就先来熟悉一下文件结构。
class文件解析:
什么是class文件:
能够被JVM识别,加载并执行的文件格式。
如何生成一个class文件:
是不是只有java文件才能够生成class文件呢?其实不是的,看下面这张图就晓得了:
对于class文件的生成一般是由两种方式来完成的:IDE自动生成、javac命令,这里主要是演示一下javac命令的方式,对于它在我们学习j2se的时候已经经历过了,这里再来温故一下:
新建一个最简单的java文件,里面内容如下:
然后编译生成class字节码文件:
然后运行:
另外对于javac命令还可以指令编译的JDK版本,如下:
class文件的作用:
用一句话描述:“记录一个类文件的所有信息”,class文件的信息是远远多于java源代码的信息的,比如说我们在写java代码时并没有定义"this"、"super"关键字,但是确能够使用它们去调用当前类的方法或父类的方法,这是因为在生成class字节码文件的时候JVM帮我们记录了this和super关键字,所以从这点也能体会到class字节码文件的信息要远远多于Java源代码所看到的信息。
class文件格式详解:
- 一种8位字节的二进制文件。
这个比较好理解,跟音视频格式的文件类似。 - 各个数据按顺序紧密的排列,无间隙。
这个不像有些文件可能为了读取上的方便会做一些填充,比如每80个字节是一行,这样的好处就是能让class文件更加的小,能够更快的被JVM所加载。 - 每个类或接口都单独占据一个class文件。
以上是class文件从宏观角度去分析,下面具体来了解一下它的具体格式,各个字段代表的含义,如下为class文件的所有字段:
那你怎么知道上面就是class文件格式所有的字段呢?不要着急,等对其有一个基本了解之后会做实验来验证的,下面具体一个个自上而下来了解一下其字段的含义:
magic:它其实是一个加密断,就像文件的MD5加密一样,用来判断其字节文件是否有被篡改过,如果被篡改过那么JVM会有一些处理的措施。
minor_version:表示此class文件最小能够被哪个版本的JDK所加载,也就是JDK最小适配的一个版本。
major_version:表示当前class文件由哪个版本的JDK所生成 ,比如咱们上面是由JDK1.8所生成的。
constant_pool_count:表示class文件中常量池的数量,通常来说是只有一个常量池,里面会存放许许多多的常量。
constant_pool:也就是真正存放常量的一个地方,其类型是一个结构体,如下:
access_flags:作用域标志,如class声明的pulic,private等。
this_class:这也就是为啥咱们可以直接在java源文件中使用"this"关键字的原因所在,jvm会自动生成一个这个字段。
super_class:其原理跟this_class一样。
interfaces_count/interfaces:保存了当前类实现的接口列表,interfaces_count 指的是当前类实现的接口数目,interfaces[] 是包含interfaces_count个接口的全局限定名的索引的数组,注意:只包含直接实现的接口,对于间接实现的不包含其中。
fields_count:表示类变量和实例变量的字段的数量总和。
fields:包含字段详细信息的列表,它是一个结构体类型:
methods_count:表示该类或者接口显示定义的方法的数量。
methods:包含方法信息的一个详细列表。也是一个结构体类型:
attribute_count/attributes:class文件的最后一部分是属性,它描述了该类或者接口所定义的一些属性信息。attributes_count指的是attributes列表中包含的attribute_info的数量。属性可以出现在class文件的很多地方,而不只是出现在attributes列表里。如果是attributes表里的属性,那么它就是对整个 class文件所对应的类或者接口的描述;如果出现在fileds的某一项里,那么它就是对该字段额外信息的描述;如果出现在methods的某一项里, 那么它就是对该方法额外信息的描述。
以上的这些字段代表的class字节码文件的所有信息,其中对于access_flags包含的具体内容下面再做一个详解:
另外对于常量池constant_pool所包含的内容下面也来说明一下:
CONSTANT_Integer_info:存放着整型相关的常量。
CONSTANT_Long_info:存放着长整型相关的常量。
CONSTANT_String_info:存放着字符符相关的常量。
当然啦其它基本类型的也有对应的来表示,上面只列举常用的三个,另外下面还有几个稍复杂的字段类型:
CONSTANT_Class_info:包含着类常量相关的一些信息。
CONSTANT_Fieldref_info:包含类成员变量相关的一些信息。
CONSTANT_Methodref_info:包含方法引用相关的一些信息。
注意:以上三个其实都是存放的一些索引信息,最终内容还是指向到具体的CONSTANT_Integer_info、CONSTANT_Long_info、CONSTANT_String_info相关的具体类型当中。
说了这么多那class文件里面的结构确实如我们所描述的那样么?这里只要真实查看一个class文件的结构就就可以验证了么,所以下面来做一下这个实验:
咱们之前已经生成了一个class文件了,如下:
那接下来就以这个文件为例进行查看,那用什么软件可以查看到class文件的格式信息呢?这里需要介绍一款软件,如下:
- 软件名称:010 Editor
- 下载地址:http://www.sweetscape.com/010editor/
主要是用来分析二进制文件的,比如之后的dex文件的格式也需要通过它,安装好之后用它打开我们要分析的Hello.class文件,如下:
其中整个class文件是包裹在这个大的结构体中:
其中struct关键字很显然就是c语言中的结构体,然后展开它:
下面具体来看一下:
而其中可以看到u2_class_index和u2_name_and_type_index都是索引信息,其真正的数据就是在之前咱们说的那些CONSTANT_Integer_info、CONSTANT_Long_info、CONSTANT_String_info等相印的字段里,接着往下再看:
其它的常量池相关的信息基本都差不多,先忽略,接着就可以看到是如下信息:
class文件的弊端:
- 内存占用大,不适合移动端。
从我们分析的class的结构信息就可以知晓,一个class文件就对应这么多东东,而一个app有成百上千个类,如果采用class文件存储的话在移动端是不太现实的,毕境移动端的资源是有限的。 - 堆栈的加栈模式,加载速度慢。
- 文件IO操作多,类查找慢。
主要是由于由于每一个class文件只存储了一个java源文件的所有信息,所以每一次加载新的class的时候都要执行一遍加载寻找。
当然它还有其它的一些弊端,正因为存在这些问题,所以dex文件格式就应运而生了,它是针对移动端专门定制的一种二进制格式,所以下面来看一下这种格式有啥特点。
dex文件解析:
什么是dex文件?
能够被DVM识别,加载并执行的文件格式。其中dex文件跟class文件类似,不仅仅只能由java源文件才能生成,其c/c++文件也可以生成dex。
如何生成一个dex文件:
当然也是由两种方式来生成啦,一是IDE自动build生成,另一种由是用dx命令生成,这里只演示dx命令手动生成dex的过程,首先当然得配制好dex命令啦,具体也就是需要将它的文件目录加到系统的path当中,这个文件一般是位于SDK的这个目录下:
配置好之后,接下来用它还是对之前的那个Hello.java进行生成,首先得要有Hello.class文件,注意:这里不能用太高版本的jdk来生成,所以这里采用javac的参数来指定稍低一些的版本来生成class,如下:
这是因为太高版本生成的class文件在一些Android手机上是无法执行的,JDK1.6生成的class文件基本上所有Android手机都能够运行,接着基于这个class文件然后生成dex文件,具体如下:
此时就已经生成好了dex文件了,接下来想办法让它在Android上能够得到执行,首先将这个dex文件导到模拟器中:
接下来进入到adb shell中去执行它:
dex文件的作用:
记录整个工程中所有类文件的信息,记住是整个工程!从这个描述来看就可以看到跟class文件的一个最大的区别就是:dex记录的是整个工程的类文件信息,而class只记录了一个java源文件的信息,
dex文件格式详解:
- 一种8位字节的二进制流文件。【跟class文件一样】
- 各个数据按顺序紧密的排列,无间隙。【跟class文件一样】
- 整个应用中所有的java源文件都放在一个dex中。【跟class最大区别的地方】
下面先来看一张图:
上面是dex整体的结构,下面来具体的看一下各个部分
字段名称 | 偏移量 | 长度(byte) | 当前例子中字段值 | 字段描述 |
magic | 0x0 | 0x8 | dex 035 | dex魔术字, 固定信息: dex\n035 |
checksum | 0x8 | 0x4 | 0x0F828C9C | alder32算法, 去除了magic和checksum 字段之外的所有内容的校验码 |
signature | 0xc | 0x14 | 58339636BED8A6CC826E A09B77D5C3A620262CD |
sha-1签名, 去除了magic、checksum和 signature字段之外的所有内容的签名 |
fileSize | 0x20 | 0x4 | 0x0000043C | 整个dex的文件大小 |
headerSize | 0x24 | 0x4 | 0x00000070 | 整个dex文件头的大小 (固定大小为0x70) |
endianTag | 0x28 | 0x4 | 0x12345678 | 字节序 (大尾方式、小尾方式) 默认为小尾方式 <--> 0x12345678 |
linkSize | 0x2c | 0x4 | 0x00000000 | 链接段的大小, 默认为0表示静态链接 |
linkOff | 0x30 | 0x4 | 0x00000000 | 链接段开始偏移 |
mapOff | 0x34 | 0x4 | 0x0000039C | map_item偏移 |
stringIdsSize | 0x38 | 0x4 | 0x00000019 | 字符串列表中的字符串个数 |
stringIdsOff | 0x3c | 0x4 | 0x00000070 | 字符串列表偏移 |
typeIdsSize | 0x40 | 0x4 | 0x00000009 | 类型列表中的类型个数 |
typeIdsOff | 0x44 | 0x4 | 0x000000D4 | 类型列表偏移 |
protoIdsSize | 0x48 | 0x4 | 0x00000006 | 方法声明列表中的个数 |
protoIdsOff | 0x4c | 0x4 | 0x000000F8 | 方法声明列表偏移 |
fieldIdsSize | 0x50 | 0x4 | 0x00000001 | 字段列表中的个数 |
fieldIdsOff | 0x54 | 0x4 | 0x00000140 | 字段列表偏移 |
methodIdsSize | 0x58 | 0x4 | 0x00000009 | 方法列表中的个数 |
methodIdsOff | 0x5c | 0x4 | 0x00000148 | 方法列表偏移 |
classDefsSize | 0x60 | 0x4 | 0x00000001 | 类定义列表中的个数 |
classDefsOff | 0x64 | 0x4 | 0x00000190 | 类定义列表偏移 |
dataSize | 0x68 | 0x4 | 0x0000028C | 数据段的大小, 4字节对齐 |
dataOff | 0x6c | 0x4 | 0x000001B0 | 数据段偏移 |
接下来用010Editor打开dex文件来查看另外两个区域:索引区和数据区:
其中先来看一下header数据区是否如我们之前所列的:
那最后的数据区在哪呢?其在在这:
class与dex文件对比:
- 本质上他们都是一样的,dex是从class文件演变而来的。
- class文件存在许多冗余信息,dex会去除冗余,并整合。
下面用一张图直观的感受一下这两者的区别: