浅谈 Android Dex 文件

本文详细介绍了如何将Java字节码转换为Dex文件,包括使用`javap`反汇编Hello.class文件,`dx`工具处理为Dex文件,以及Dex文件的格式和在AndroidTinker热修复中的应用。还提到了NDK和JVM的基础知识,强调编程实践和理论学习的重要性。
摘要由CSDN通过智能技术生成

这里也可以对 Hello.class 文件执行 javap 命令,进行反汇编。

javap -c Hello

执行结果如下:

public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object.“”😦)V
4: aload_0
5: ldc #2 // String hello! youzan
7: putfield #3 // Field helloString:Ljava/lang/String;
10: return

public static void main(java.lang.String[]);
Code:
0: new #4 // class Hello
3: dup
4: invokespecial #5 // Method “”😦)V
7: astore_1
8: aload_1
9: aload_1
10: getfield #3 // Field helloString:Ljava/lang/String;
13: invokevirtual #6 // Method fun:(Ljava/lang/String;)V
16: return

public void fun(java.lang.String);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_1
4: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
}

其中 Code 之后都是具体的指令,供 JVM 虚拟机执行。指令的具体含义可以参考 JAVA 官方文档。

从 .class 到 .dex

上面生成的 .class 文件虽然已经可以在 JVM 环境中运行,但是如果要在 Android 运行时环境中执行还需要特殊的处理,那就是 dx 处理,它会对 .class 文件进行翻译、重构、解释、压缩等操作。

dx 处理会使用到一个工具 dx.jar,这个文件位于 SDK 中,具体的目录大致为 你的SDK根目录/build-tools/任意版本 里面。使用 dx 工具处理上面生成的Hello.class 文件,在 Hello.class 的目录下使用下面的命令:

dx --dex --output=Hello.dex Hello.class

执行完成后,会在当前目录下生成一个 Hello.dex 文件。这个 .dex 文件就可以直接在 Android 运行时环境执行,一般可以通过 PathClassLoader 去加载 dex 文件。现在在当前目录下执行 dexdump 命名来反编译:

dexdump -d Hello.dex

执行结果如下(部分区域的含义已经在下面描述):

Processing ‘Hello.dex’…
Opened ‘Hello.dex’, DEX version ‘035’

------ 这里是编写的 Hello.java 的类的信息 ------
Class #0 -
Class descriptor : ‘LHello;’
Access flags : 0x0001 (PUBLIC)
Superclass : ‘Ljava/lang/Object;’
Interfaces -
Static fields -
Instance fields -
#0 : (in LHello;)
name : ‘helloString’
type : ‘Ljava/lang/String;’
access : 0x0002 (PRIVATE)

------ 下面区域描述的是构造方法的信息。7010 0400 0100 1a00 0b00 之类的数字就是方法中的代码翻译成的指令。Dalvik 使用的是16位代码单元,所以这里就是4个数字为一组,每个数字是16进制。invoke-direct 这些是前面指令对应的助记符,也代表着这些指令的真正操作。如果对这些指令转化感兴趣可以去https://source.android.com/devices/tech/dalvik/instruction-formats 查看 ------
Direct methods -
#0 : (in LHello;)
name : ‘’ — 方法名称:这个很明显就是构造方法 —
type : ‘()V’ — 方法原型,()里面表示入参,()后面表示返回值,V代表void—
access : 0x10001 (PUBLIC CONSTRUCTOR) — 方法访问类型 —
code -
registers : 2 — 方法使用的寄存器数量 —
ins : 1 — 方法入参,方法除了我们定义的参数以外,系统还会默认带一个特殊参数 —
outs : 1
insns size : 8 16-bit code units — 指令大小 —
000148: |[000148] Hello.😦)V
000158: 7010 0400 0100 |0000: invoke-direct {v1}, Ljava/lang/Object;.😦)V // method@0004
00015e: 1a00 0b00 |0003: const-string v0, “hello! youzan” // string@000b
000162: 5b10 0000 |0005: iput-object v0, v1, LHello;.helloString:Ljava/lang/String; // field@0000
000166: 0e00 |0007: return-void
catches : (none)
positions :
0x0000 line=1
0x0003 line=2
locals :
0x0000 - 0x0008 reg=1 this LHello;

#1 : (in LHello;)
name : ‘main’
type : ‘([Ljava/lang/String;)V’
access : 0x0009 (PUBLIC STATIC)
code -
registers : 3
ins : 1
outs : 2
insns size : 11 16-bit code units
000168: |[000168] Hello.main:([Ljava/lang/String;)V
000178: 2200 0000 |0000: new-instance v0, LHello; // type@0000
00017c: 7010 0000 0000 |0002: invoke-direct {v0}, LHello;.😦)V // method@0000
000182: 5401 0000 |0005: iget-object v1, v0, LHello;.helloString:Ljava/lang/String; // field@0000
000186: 6e20 0100 1000 |0007: invoke-virtual {v0, v1}, LHello;.fun:(Ljava/lang/String;)V // method@0001
00018c: 0e00 |000a: return-void
catches : (none)
positions :
0x0000 line=5
0x0005 line=6
0x000a line=7
locals :

Virtual methods -
#0 : (in LHello;)
name : ‘fun’
type : ‘(Ljava/lang/String;)V’
access : 0x0001 (PUBLIC)
code -
registers : 3
ins : 2
outs : 2
insns size : 6 16-bit code units
000190: |[000190] Hello.fun:(Ljava/lang/String;)V
0001a0: 6200 0100 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001
0001a4: 6e20 0300 2000 |0002: invoke-virtual {v0, v2}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0003
0001aa: 0e00 |0005: return-void
catches : (none)
positions :
0x0000 line=10
0x0005 line=11
locals :
0x0000 - 0x0006 reg=1 this LHello;

source_file_idx : 1 (Hello.java)

到此为止,已经完成了将 Java 代码转变成 Dalvik 可执行的文件,即 dex。

Dex 文件的具体格式

现在来分析一下 Dex 文件的具体格式,就像 MP3,MP4,JPG,PNG 文件一样,Dex 文件也有它自己的格式,只有遵守了这些格式,才能被 Android 运行时环境正确识别。

Dex 文件整体布局如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些区域的数据互相关联,互相引用。由于篇幅原因,这里只是显示部分区域的关联,完整的请去官网自行查看相关数据整理。下图中的各字段都在后面的各区域的详细介绍中有具体介绍。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面将分别对文件头、索引区、类定义区域进行简单的介绍。其它区域可以去 Android 官网了解。

文件头

文件头区域决定了该怎样来读取这个文件。具体的格式如下表(在文件中排列的顺序就是下面表格中的顺序):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

id 区

id 区存储着字符串,type,prototype,field, method 资源的真正数据在文件中的偏移量,我们可以根据 id 区的偏移量去找到该 id 对应的真实数据。

字符串 id 区域

这个区块是一个偏移量列表,每个偏移量对应了一个真正的字符串资源,每个偏移量占32位。我们可以通过偏移量找到对应的实际字符串数据。具体格式如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最终这个偏移的位置应该是落在数据区的。找到这个偏移量的位置后,根据下面的格式就可以读取出这个字符串资源的具体数据:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

类型 id 区

这个区块是一个索引列表,索引的值对应字符串id区域偏移量列表中的某一项。数据格式如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们要找到某个类型的值,需要先根据类型id列表中的索引值去字符串id列表中找到对应的项,这一项存储的偏移量对应的字符串资源就是这个类型的字符串描述。

方法原型 id 区

这个区块是一个方法原型 id 列表,数据格式为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

成员 id 区

这个区块存储着原型 id 列表,数据格式为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方法 id 区

这个区块存储着方法 id 列表,数据格式为: 这个区块存储着原型 id 列表,数据格式为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

类定义区

这个区域存储的是类定义的列表,具体的数据结构如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解析 dex 文件的工具

这里推荐一个可以解析 dex 文件的工具 010 Editor。它可以通过预置的模板让我们更清晰的了解 dex 文件的格式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dex 文件在 Android Tinker 热修复中的应用

在目前的主流的 Android 热修复方案中,Tinker有免费、开源、用户量大等优点,因此在有赞也是基于 Tinker 搭建 Android 热修复服务。Tinker 热修复的主要原理就是通过对比旧 APK 的 dex 文件与新 APK 的 dex 文件,生成补丁包,然后在 APP 中通过补丁包与旧 APK 的 dex 文件合成新的 dex 文件。流程如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注:图片来源于 Tinker 官网

补丁包的生成

Tinker 官方使用自研一套合成方案,就是 DexDiff。它基于 Dex文件格式的特性,具有补丁包小,消耗内存小等优点。在 DexDiff 算法中,会根据 Dex文件的格式,将 Dex 文件划分为不同的区块类,如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些区块有一个统一的数据结构,主要的数据有区块对应的实际数据类型及在文件中的偏移量。如下图:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

评论里面有些同学有疑问关于如何学习material design控件,我的建议是去GitHub搜,有很多同行给的例子,这些栗子足够入门。

有朋友说要是动真格的话,需要NDK以及JVM等的知识,首现**NDK并不是神秘的东西,**你跟着官方的步骤走一遍就知道什么回事了,无非就是一些代码格式以及原生/JAVA内存交互,进阶一点的有原生/JAVA线程交互,线程交互确实有点蛋疼,但平常避免用就好了,再说对于初学者来说关心NDK干嘛,据鄙人以前的经历,只在音视频通信和一个嵌入式信号处理(离线)的两个项目中用过,嵌入式信号处理是JAVA->NDK->.SO->MATLAB这样调用的我原来MATLAB的代码,其他的大多就用在游戏上了吧,一般的互联网公司会有人给你公司的SO包的。
至于JVM,该掌握的那部分,相信我,你会掌握的,不该你掌握的,有那些专门研究JVM的人来做,不如省省心有空看看计算机系统,编译原理。

一句话,平常多写多练,这是最基本的程序员的素质,尽量挤时间,读理论基础书籍,JVM不是未来30年唯一的虚拟机,JAVA也不一定再风靡未来30年工业界,其他的系统和语言也会雨后春笋冒出来,但你理论扎实会让你很快理解学会一个语言或者框架,你平常写的多会让你很快熟练的将新学的东西应用到实际中。
初学者,一句话,多练。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

写多练,这是最基本的程序员的素质,尽量挤时间,读理论基础书籍,JVM不是未来30年唯一的虚拟机,JAVA也不一定再风靡未来30年工业界,其他的系统和语言也会雨后春笋冒出来,但你理论扎实会让你很快理解学会一个语言或者框架,你平常写的多会让你很快熟练的将新学的东西应用到实际中。**
初学者,一句话,多练。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值