前言

相信大家都熟悉dex文件,把一个apk给解压缩,就会得到一堆dex文件,但是这些dex文件是怎么来的,又有什么用,为什么这样设计,有进行思考过吗

俗话说知其然,知其所以然,本篇文章开始探究一下这些底层实现细节。

正文

不同的虚拟机

JVM

JVM是Java Virtual Machine的简称,即Java虚拟机,它本质是一层软件抽象,在这之上才可以运行Java程序。Java文件经过编译后会生成JVM字节码,和C语言编译后生成的汇编语言不同,C编译成的汇编语言可以直接在硬件上跑,但是Java编译生成的字节码是在JVM上跑,需要由JVM把字节码翻译成机器指令。

也是由于这个JVM在操作系统上屏蔽了底层实现的差异,从而有了Java的跨平台特性。

DVM

DVM是Dalvik Virtual Machine的简称,是Android4.4及以前使用的虚拟机,所有Android程序都运行在Android系统进程中,每个进程对应着一个Dalvik虚拟机实例。

JVM和DVM都提供了对对象生命周期管理,堆栈管理,安全和异常管理及垃圾回收等重要功能。

但是DVM却不能和JVM一样能直接运行Java字节码,它只能运行.dex文件,而这个.dex文件则是由Java字节码通过Android的dx工具生成的文件。

ART

ART是Android Runtime,在Android5.0开始使用ART虚拟机来替代Dalvik虚拟机,为什么Google要换Android程序运行的虚拟机呢 因为ART虚拟机更优秀。

前面说了Dalvik虚拟机会在APP打开时去运行.dex文件,而这个是实时的,也就是JIT特性(Just In Time),这也就会导致在启动APP时会先将.dex文件转换成机器码,这就导致了APP启动慢的问题。

而ART虚拟机有个很好的特性叫做AOT(ahead of time),这个特性可以在安装APK的时候将dex直接处理成可直接供ART虚拟机使用的机器码,ART虚拟机将.dex文件转换成可直接运行的.oat文件,而且ART虚拟机天生支持多dex,所以ART虚拟机可以很大提升APP的冷启动速度。

除了这个优点外,ART还提升了GC速度,提供功能更全面的Debug特性,但是缺点也就是APK安装速度慢,占用的空间多。

生成和查看dex文件

前面说了dex文件是给Android手机的虚拟机来使用的,所以我们来看看如何生成和查看一个dex文件。

先编写一个简单的.java文件:

public class HelloWorld {  int a = 0;  static String b = "HelloDalvik";  public int getNumber(int i, int j) {  int e = 3;  return e + i + j;  }  public static void main(String[] args) {  int c = 1;  int d = 2;  HelloWorld helloWorld = new HelloWorld();  String sayNumber = String.valueOf(helloWorld.getNumber(c, d));  System.out.println("HelloDex!" + sayNumber);  }
}

然后使用javac命令来编译.java文件为.class,注意这里必须使用Java 8,而不能使用Java 11,如下图专门使用Java 8编译的结果(原来Windows环境变量是Java 11,后续的dex解析有误):

有了.class文件后,就是Android的dx工具,该工具一般在下面目录:

//也就是sdk目录下的build-tools文件夹中
D:\Users\wayee\AppData\Local\Android\sdk\build-tools\30.0.3\dx.bat

使用dx工具对.class文件进行处理:

然后会生成一个.dex文件,直接打开这个dex文件它是十六进制编码的文件,看不出任何有用信息,这时就需要一个专门来看这个的工具,这里推荐使用 010 Editor 这个工具,直接把.dex文件拖入工具:

注意这里选择的模板就是DEX.bt,然后就可以按照DEX的格式来分析这些字节是什么意思了,所以看懂dex文件必须要了解DEX文件的格式。

Dex文件格式

看到这里就必须要清楚一个基本概念了,也就是平时使用Java编写的文件,这里给编译打包成dex文件,那这个dex文件就必须要包含这个Java文件的所有信息,那是按照Java文件顺序一行一行保存为字节码还是其他什么方式呢

所以想知道编译器是如何在编译Java文件后保存信息的,就必须要清楚Dex文件格式。

我们可以直接在刚刚010 Editor软件中看到Dex.bt即Dex文件的格式,其格式如下:

当然也可以去Android源码官网看一下Dex的格式:

看了上面dex文件的格式,其大致可以分为3个区域,分别是文件头、索引区和数据区,那我们就来挨个分析这几个区域有什么作用,以及是如何保存编译后的Java文件。

header 文件头

头文件它包含了这个dex文件的几乎所有信息,所以它的信息非常多,其格式如下:

然后这时就直接点击010 Editor下面的dex_header部分:

其中上面的红框就是文件头的数据,而下面的红框就是文件头的格式,我们来挨个分析一下。

1、Magic value,即魔数,这个就是用来失败dex这种文件的,可以判断当前的dex文件是否有效,其值是固定死的:

转换成ASCII也就是dex.035, 所以凡是dex文件都是这个开头,否则就是错误的dex文件。

2、checksum,dex文件的校验和,它可以判断dex文件是否损坏或者篡改,占用4个字节,注意这里是采用小字节序的编码方式,即低位上存储的就是低字节内容,可以看一下:

会发现这里的值和二进制保存是相反的。

3、SHA1签名,也就是把整个dex文件用SHA-1签名得到的一个值,占用20个字节。

4、fileSize,整个文件的大小,占用4个字节,看一下值这里是:

十进制是1204,换算成16进制就是4B4,我们再来看看这个dex文件的长度:

这里长度也是4B4。

5、headerSize,表示头结构的大小,占用4个字节,这个就不截图看了。

6、endian_tag,表示字节序,这里具体的值就2个,标准的.dex格式采用小段字节序,但具体实现可能会选择执行字节交换,所以这个改变就由这个tag来判断。

7、linkSize和linkOff,这2个字段指定了链接段的大小和文件偏移,通常情况下他们都是0,linkSize为0表示为静态链接。

从这里开始就会发现有off这个字段,这是啥意思呢,其实也就是文件偏移量,也就是从这个文件第多少位置开始表示的值。

8、mapOff,这个字段表示DexMapList的文件偏移,这里我们先不多介绍,后面再说,这里值是:

换成16进制就是414h。

9、stringIdsSize和stringIdsOff,这2个字段指定了dex文件中所有用到的字符串的个数和位置偏移,注意这里指的是位置偏移,而不是真正的字符串值。

我们来看看size是多少:

会发现一共有28个字符串,而其值的偏移从112开始,而这个112是不是有点熟悉,112是整个dex头的大小,也就说明在头部之后第一部分就是字符串索引,这里之所以叫做索引也很合理,从112开始的n个字节保存的是程序用到的字符串的偏移量,注意这里不是字符串,只是各个字符串的偏移量。

这时你可能会疑惑,这28个字符串的偏移量该如何存放以及值是多少,我们完全不用担心,还是打开010 Editor软件,选中数据结构是dex_string_ids即可:

会发现从70h开始,开始保存的每个字符串的偏移量,而这个偏移量对应的就是最后面部分的值,我们还是拿第一个字符串来说:

会发现这里的偏移量,我们转到偏移量会发现:

这里是用了10个字节来保存了一个字符串,这个字符串是"clinit",我们暂时不考虑这个字符串是啥意思,这里采用了一种叫做uleb的数据结构,来动态保存字符串长度,这里我们暂时不考虑细节。

其实从这个字符串保存的方法来看,我们已经能大概看出是如何保存的了。首先在头部保存字符串大小,以及字符串索引的偏移量,然后再遍历索引找到每个字符串。

比如上面我们的代码,在这里保存的字符串是如下:

10、typeIds和typeIdsOff,有了上面字符串保存的逻辑,这个就是类的类型的数量和位置偏移,也都是占用4个字节,我们还是来看看值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kW77mc6C-1653642835067)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b0eb703dded4459a2e3a9b282327f8f~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

一共用到了9个类型,但是注意这里就没必要像保存字符串一样了,记录每个类型的偏移量,再去偏移的地方取值,这里类型的描述符已经在前面字符串变量中都进行描述过了,所以这里保存的是字符串的索引,我们来看看:

找到上面对应的偏移位置,我们发现第一个类型值是0x5,然后我们再去前面的字符串索引找到下标为5的字符串:

会发现这里的值是I,也就是第一个类型,依次类推,所有的类型如下:

会发现这几个类型的字符串描述在前面字符串列表中都保存过了,这样设计也可以减小查询操作、节省内存。

11、protoIdSize和protoIdOff,这个表示的是方法原型的个数和位置偏移,会发现上面dex文件中有7个方法原型,这里图就不截了,来看一下这7个方法原型都保存了哪些数据:

其实不难理解,想表示一个方法原型不外乎就是方法名、返回值和参数,其中参数可能是多个,所以会有多个类型索引,这里具体的数据结构就不细说了,大体意思理解即可,来看看7个方法原型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9aNGJ56-1653642835081)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0de94620959b4b9e92178e7999594b2b~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

这里的方法也就是前面java文件中有使用到的。

12、fieldSize和fieldOff,这2个字段就比较简单了,表示java文件中字段的信息,从头部数据结构中会发现有3个字段,我们直接看一下字段索引的数据:

会发现到这里时,信息表示就变的简单了,因为你想表示一个字段,不外乎就是类型、其类的类型、以及自己描述的字符串,而由于前面我们已经得到了字符串索引和类型索引,所以这里数据结构中的值直接使用前面定义过的索引即可。

还是看一下定义的所有字段的值:

13、methodSize和methodOff,这2个字段就比较熟悉了,表示了方法,而方法的表示也是需要几个要点,比如方法所在的类、方法的声明以及方法名,而类在之前类型索引定义过了,方法声明也声明过了,以及方法名也就是之前定义的字符串索引,所以这里我们就不细看其数据结构了,直接看一下我们前面写的java文件有多少个方法:

这里一共有10个方法。

14、classDefsSize和classDefsOff,这2个字段表示类定义的相关信息,类的信息就比较多了,包括类的修饰符、父类、接口、注解、静态元素等等,我们也还是通过010 Editor来看一下class都保存了哪些信息:

可以发现还是有不少信息的。

到这里我们基本就可以把一个类的信息都整清楚了,我们使用一张图来表示:

上图虽然只是表示了文件头的信息,但是我们知道有了这些文件头的信息,根据偏移量便可以获取到其保存的值。

Dex文件格式总结

看了文件头的定义,并且明白其值的意义,便也就熟悉了整个Dex格式的保存原理,我们这里看一张图:

这里除了文件头还有索引区和数据区,其中索引区的偏移量已经在文件头中定义,而数据区则保存着类的定义以及索引区中的数据,而最下面的链接数据区则是一些静态库或者动态库的链接。

总结

本篇内容有点多,但是还是很好理解的,首先就是虚拟机,在Android系统的虚拟机需要读取dex文件,而这个dex文件是由我们编写的.java文件编译而来,所以dex文件应当保存.java文件的所有信息。

而保存这些信息的方法就像是文件头保存大致地址,索引区保存具体地址,数据区是真的地方,通过这种方式就可以完整的保存一个java文件的信息。

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

需要的直接点击文末小卡片可以领取哦!我免费分享给你,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)

Android学习PDF+架构视频+面试文档+源码笔记

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT大厂面试题(有解析)

领取地址:

Android Dex文件详解相关推荐

  1. Android - Manifest 文件 详解

    Manifest 文件 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/20899281 Manifest可以定义应用程序及其 ...

  2. Android.mk文件详解介绍

    Android.mk 编译文件是用来向 Android NDK描述你的 C,C++源代码文件的,   这篇文档描述了它的语法.在阅读下面的内容之前,假定你已经阅读了 docs/OVERVIEW.TXT ...

  3. Android.bp文件详解

    本文文档在如下位置,如有需要可以下载: https://download.csdn.net/download/fanx9339/12542402 Android.bp文件是什么? Android.bp ...

  4. Android清单文件详解(三)----应用程序的根节点application

    <application>节点是AndroidManifest.xml文件中必须持有的一个节点,它包含在<manifest>节点下.通过<application>节 ...

  5. android .so文件详解以及兼容性

    Android 设备的CPU类型通常称为ABIs 问题描述 解决方法 1解决之前的截图 2解决后的截图 3解决方法 4建议 为什么你需要重点关注so文件 App中可能出错的地方 其他地方也可能出错 使 ...

  6. Android AndroidManifest 文件详解

    目录 一.概述 二.标签和属性 1.标签 2.属性 一.概述 AndroidManifest 文件,简称为 Manifest 文件,在 AndroidManifest 文件中,它告诉系统我们 App ...

  7. Android rc 文件详解

    应用中添加使用rc 0. 在Android.mk 同目录下新建文件haha.sh (文件名任意),执行shell 操作, 以下简单举例 #!/bin/sh rm -rf /system/etc/xxx ...

  8. Android 清单文件 详解

    转载于:https://www.cnblogs.com/mohe/archive/2013/03/31/2991642.html

  9. 生成jni的android.mk,Android Studio 3.5版本JNI生成SO文件详解

    学习在于记录,把自己不懂得容易忘记得记录下,才是最好得选择. 废话不多说,想要在Android开发中嵌入c/c++代码,直接开始如下步骤 1.创建需要调用的Java类 在你某个指定的包下创建如下类pa ...

  10. android 7 apk 安装程序,Android安装apk文件并适配Android 7.0详解

    Android安装apk文件并适配Android 7.0详解 首先在AndroidManifest.xml文件,activity同级节点注册provider: android:name="a ...

最新文章

  1. linux安装virtualbox命令,在Linux中从命令行查找Virtualbox Version的方法
  2. 日常生活 -- 感悟
  3. extract local variale 和 jsp中查找选中内容的快捷键
  4. 0408~送给小伙伴的汉堡包
  5. C++中size_type类型详解
  6. 2019-12-17 drivers/clocksource/arm_arch_timer.c
  7. Spring Cloud消息驱动整合
  8. 内存泄露分析 - 收藏集 - 掘金
  9. 阿斯克码表ACSII对照表
  10. 文件另存为GBK编码格式
  11. 从excel表格生成ArcGIS Pro样式符号
  12. 任正非:管理上的灰色,是我们的生命之树
  13. 微信公众平台开发--入门了解
  14. Eclipse 工具上Springboot项目的简单 增删改查 的搭建
  15. s7五杀大数据英雄_王者七大记录,五杀最多的英雄竟然有他?这英雄跑的比关羽还快!...
  16. 第一行代码Android技巧1——知晓当前是在哪一个活动
  17. m2硬盘写入速度测试软件,实测:M2固态硬盘换个插槽传输速度竟然提升了约1000M!...
  18. 一次性修改多张图片尺寸
  19. java 有序的list_Java 中的 List —— 有序序列
  20. 1833. 雪糕的最大数量

热门文章

  1. 万能ABAP程序修改器
  2. bootstrap插件bootbox参数
  3. 生成登录验证码,点击更换验证码图片
  4. 【开源项目分享】使用select、多线程完成的多人联机对战五子棋小游戏(C语言实现)
  5. 【python实战】制作微信动态名片
  6. 旅游B2B2C系统解决方案
  7. 解决qt.qpa.xcb: could not connect to display问题
  8. qt.qpa.plugin: Could not find the Qt platform plugin “xcb“ i
  9. Maven的setting仓库配置
  10. 【福利】小米手机修改MAC地址教程