当前位置:
文档之家› Class转储Dex的原理解析
Class转储Dex的原理解析
Runt ime Invi sibleAnnot at ions RuntimeVisibleAnnotations
Synthetic Signature SourceDebugExtension SourceFile
ConstantValue Deprecated
Runt ime Invi sibleAnnot at ions RuntimeVisibleAnnotations
Class 转储 Dex 的原理解析
导语:Dex 文件对于 Android 开发者来说并不陌生,其作用是充当一个 Android 应用的
可执行文件。Android 应用基于 Java 虚拟机(JVM),执行字节码,我们开发 Android 应用 也是通过 Java 语言,经过编译后生成 Class 文件,然后通过 Dex 最终打包到 Apk。那么, Class 文件是如何转储到 Dex 中的呢?基于 Android Gradle 的构建,本文首先简述了 Class 文件、Dex 文件的结构定义,说明二者在结构方面的联系,然后详细描述了 Android Gradle 转储 Class 到 Dex 的过程,最后,通过可视化编辑器打开 Dex 文件,让开发者真实查看 Dex 文件内容,深入 Dex 的世界。
从以上 Dex 文件格式定义来看,Dex 文件格式类似于 Class 文件格式,都存 储了 Java 的常量、类、方法、变量、字节码等,集成了 Class 文件紧密存储的 特性。不同之处在于,Dex 文件具有更高的存储量,更丰富的存储类型。那么 Dex 文件与 Class 文件到底是什么关系,以及如何生成 Dex 文件呢?以下小节给予详 细的说明。
interfaces_count
fielfields
u2
methods_count
method_info methods
u2
attributes_count
attribute_info attributes
fields_count 1 methods_count 1 attributes_count
constant_pool access_flags
this_class super_class interfaces
fields methods
attributes
Class Field
Boot strap Methods Deprecated
E nclosi ngMe thod Inne r Class es
其中,utf16_size 为该字符串的长度,data 为字符串的具体内容,以 0 为结尾。
Dex 文件格式继承了 Class 文件的紧密存储特性,很多其他区域的对象都通过常 量的索引值来实现对常量的引用,例如 type_ids 中的类型名称,proto_ids 中 的方法原型字符串,filed_ids 中的字段名等等。
类方法索引,记录了方法所属类,声明类型以及方法 method_id_item[] 名等信息。
class_defs
类定义数据,记录了指定类的各类信息,包括接口, class_def_item[] 超类,类数据偏移量等。
data
type_id_item[] 类型数据索引,记录了各个类型的字符串索引。
type_ids
StringDataItem ClassDefItem ClassDataItem FieldIdItem MethodIdItem MethodHandleItem ProtoIdItem AnnotationsDirectoryItem CallSiteItem
CodeItem DebugInfoItem EncodedArrayItem
用于存储其它结构体信息。例如成员方法(Method),包括名称、参数列表、字节码、行号
等都将按照方法的结构体依次存储到 method_info 列表中。
Java 开发过程,一般不需要关注 Class 文件。经过编译后的 Java 源码文件通过文本的方
式打开很难再看到详细的代码逻辑。但 JVM 规范为每个字节码提供了相应的助记符,基于
属性、成员变量、成员方法、甚至内部类等。其中成员变量又有静态、非静态、访问属性、
常量等区分。成员函数又有静态函数、抽象函数、重载函数、同步锁、异常列表等等属性。
一个 Java 类信息是如何准确存储到 Class 文件中的呢?这就要借助于 Class 文件的结构定
义,如下表:
类型 u4 u2 u2 u2 cp_info u2 u2 u2 u2 u2 u2
存储字符串常量列表,每个 Item 只包含一个字段:stringDataOff,用于指定该字
符 串 常 量 在 文 件 中 的 偏 移 地 址 。 stringDataOff 指 向 的 偏 移 地 址 为 结 构 体
StringDataItem,如下: struct string_data_item { uleb128 utf16_size; ubyte data; }
ubyte[]
数据区,保存着各个类的数据。
link_data
ubyte[]
静态连接数据。
表 2 Dex 文件格式
以下,对 Dex 文件格式的一些重要区域进行解释。
header:文件头区域,存储内容包括 magic、checksum、signature、fileSize、
headerSize、endianTag 以及其他各个区域的头信息。不同于 Class 文件的魔数,
type_id_item[] 类型数据索引,记录了各个类型的字符串索引。
proto_id
函数原型数据索引,记录了方法声明的字符串,返回 proto_id_item[] 类型和参数列表。
field_ids
field_id_item[]
字段数据索引,记录了所属类,声明类型和方法名等 信息。
method_ids
项目主要存储反射类型的字段和方法。ProtoIdItem,原型定义,主要存储每个 方法的签名,包括参数列表,返回值。
常量池(constant_pool),是 Class 文件中定义的全部常量,包括字符串常 量、类名、字段名、方法名等。在转储的过程中,这些数值都将转储到 Dex 的 StringDataItem 。 除 常 量 池 中 描 叙 的 字 符 串 转 换 到 StringDataItem 外 , this_class、super_class、interfaces、methods、attributes 等描叙的其他常 量也都转储到了 StringDataItem。StringDataItem 可以认为是 Dex 文件的常量 池,但却比 Class 文件的常量池存储更广泛的常量。对常量池的应用于 Class 文 件相同,都通过常量池索引。
Dex 文件的魔数占用 8 个字节,一般以“Dex”开头,后面跟随版本信息。headerSize
表示头区域的大小,以字节为单位,固定为 0x70。其他各个区域的头信息一般成
对出现,包括区域元素个数,以及该区域相对于文件开头的偏移地址。
string_ids:字符串常量区域,存储一个 Dex 文件中全部的字符串常量,该区域
method_ids: 方法区域,存储 Dex 文件内全部的方法,格式定义如下: struct method_id_item { ushort class_idx; ushort proto_idx; uint name_idx; }
其中,class_idx 为指向 type_ids 区域的索引,proto_idx 为指向 proto_ids 区
3. 从 Class 到 Dex
Class 文件的主体包括类定义、字段、方法、字节码、各种属性等,这些信
息在 Dex 文件中都有定义。其实,Dex 文件就是 Class 文件的一个容器,能够承 载一个或者多个 Class 文件。
Dex 文件对 Class 文件的容纳并不是简单的数据流拷贝,而是一种内容的重 组。以下图详细描述了类文件到 Dex 文件的转换:
Synthetic Signature
Method
Annota tionDefaul t Code
Deprecated Exceptions RuntimeVisibleAnnotations Param eterAnnot atio ns Synthetic Signature
Code
LineNumberTable LocalVariableTable LocalVariableTypeTable
此,Class 文件也可以通过反编译工具查看。
像主版本号(majar_version)、次版本号(minor_version),以及文件开头的魔法数都是 Java
运行时刻的检查项,也是 Class 文件运行过程中经常报的 Java 版本问题。另外,常量池 cp_info
存储 Class 中全部的常量,如类名称、方法名称、属性名称等都放在该区域,该区域的长度
通过 u2 格式存储,这也就意味着该区域的长度最大为 65536,这也就解释了为什么一个 Java
文件中的方法数不能超过 65536,过多的方法将导致 Java 文件编译失败。
Class 文件的结构化存储为运行时刻的高效解析提供了遍历。虽然 Java 是一种解释执行
语言,具备动态链接的功能,但为提高效率,可以说,Java 文件是面向开发者的,Class 文
域的索引,name_idx 为指向 string_ids 区域索引;class_idx 和 proto_idx 的 数据类型为 ushort,最大索引值为 65535,这就意味着,method_ids 最大只能 指向的索引为 65535,大于该索引的方法将不能被存储,Dex 文件生成失败,这 就是 Android 开发过程中经常遇到的 MultiDex 问题。
HeaderItem MapItem