这里是两篇文章放在一起了:
第一篇文章:
在了解 Android 运行时之前,我们需要了解什么是运行时环境以及一些基本概念,即 Java 虚拟机(JVM)和 Dalvik 虚拟机(DVM)的功能。
什么是运行时?
简单来说,运行时就是一个供操作系统使用的系统,它负责将你用高级语言(比如 Java)编写的代码转换成 CPU/处理器能够理解的机器码。
运行时由你的程序运行时所执行的指令构成,尽管本质上它们不属于程序代码的任何一部分。
CPU (或者更通用的说法电脑)只能够理解机器语言(二进制代码),所以为了使程序能够在 CPU 上运行,就必须将它们翻译成机器码,这一工作由翻译器完成。
这里按序列出历代翻译器:
1.汇编器
它直接将汇编语言翻译成机器码,所以它的速度非常快。
2.编译器
它将源码翻译成汇编语言,然后再用汇编器转换成机器码。这种方式编译过程很慢但是执行速度很快。但是使用编译器最大的问题是编译出来的机器码依赖于特定的平台。换句话说,在一台机器上可以运行的代码在另一台不同的机器上可能就无法运行。
3.解释器
它在执行程序时才翻译代码。由于代码翻译是在执行阶段才发生,所以执行速度很慢。
JAVA 代码是怎么执行的?
为了使代码和平台无关,JAVA开发了 JVM,即 Java 虚拟机。它为每一个平台开发一个 JVM,也就意味着 JVM 是和平台相关的。Java 编译器将 .java 文件转换成 .class文件,也就是字节码。最终将字节码提供给 JVM,由 JVM 将它转换成机器码。
这比解释器要快但是比 C++ 编译要慢。
Android 代码是怎么执行的
在 Android 中,Java 类被转换成 DEX 字节码。DEX 字节码通过 ART 或者 Dalvik runtime 转换成机器码。这里 DEX 字节码和设备架构无关。
Dalvik 是一个基于 JIT(Just in time)编译的引擎。使用 Dalvik 存在一些缺点,所以从 Android 4.4(Kitkat)开始引入了 ART 作为运行时,从 Android 5.0(Lollipop)开始 ART 就全面取代了Dalvik。Android 7.0 向 ART 中添加了一个 just-in-time(JIT)编译器,这样就可以在应用运行时持续的提高其性能。
重点:Dalvik 使用 JIT(Just in time)编译而 ART 使用 AOT(Ahead of time)编译。
下图描述了 Dalvik 虚拟机和 Java 虚拟机之间的差别。
Just In Time (JIT)
使用 Dalvik JIT 编译器,每次应用在运行时,它实时的将一部分 Dalvik 字节码翻译成机器码。在程序的执行过程中,更多的代码被被编译并缓存。由于 JIT 只翻译一部分代码,它消耗的更少的内存,占用的更少的物理存储空间。
Ahead Of Time(AOT)
ART 内置了一个 Ahead-of-Time 编译器。在应用的安装期间,他就将 DEX 字节码翻译成机器码并存储在设备的存储器上。这个过程只在将应用安装到设备上时发生。由于不再需要 JIT 编译,代码的执行速度要快得多。
由于 ART 直接运行的是应用的机器码(native execution),它所占用的 CPU 资源要少于 使用 JIT 编译的 Dalvik。由于占用较少的 CPU 资源也就消耗更少的电池资源。
ART 和 Dalvik 一样使用的是相同的 DEX 字节码。编译好的应用如果使用 ART 在安装时需要额外的时间用于编译,同时还需要更多的空间用于存储编译后的代码。
Android 为什么要使用虚拟机?
Android 使用虚拟机作为其运行环境是为了运行 APK 文件构成的 Android 应用。它的优点有:
- 应用代码和核心的操作系统分离。所以即使任意一个程序中包含恶意的代码也不会直接影响系统文件。这使得 Android 操作系统更稳定可靠。
- 它提高了跨平台兼容性或者说平台独立性。这意味着即使某一个应用是在 PC 上编译的,它也可以通过虚拟机在移动平台上执行。
ART 的优点
- 应用运行更快,因为 DEX 字节码的翻译在应用安装是就已经完成。
- 减少应用的启动时间,因为直接执行的是 native 代码。
- 提高设备的续航能力,因为节约了用于一行一行解释字节码所需要的电池。
- 改善的垃圾回收器
- 改善的开发者工具
ART 的缺点
- 应用安装需要更长的时间,因为 DEX 字节码需要在安装时就翻译成机器码。
- 由于在安装时时生成的 native 机器码是存储在内部存储器上,所以需要更多的内部存储空间。
结论
DEX 是专门为 Android 设计的一种字节码格式,主要是为了消耗更少的内存进行优化。ART 是为了在低端设备上运行多个虚拟机而开发的,这一目的通过使用 DEX 字节码实现。它使得应用的 UI 反应更及时。这就是我个人的全部观点。更多关于 ART 和 Dalvik 的细节可以参考Android 官方文档。
第二篇文章:
关于JVM、Dalvik、ART、Dex的一点理解
JVM是Oracle用来运行java代码的虚拟机,将code转成机器字节码。
Dalvik是Google设计的专门用来在android平台上运行的虚拟机。能运行.dex的java程序。.dex是专门用于在Dalvik上运行的一种压缩格式。在一台android机上,Dalvik会为每个程序单独开辟一个Linux进程,防止一个程序崩溃引起其他程序关闭。
由于每个app启动,都会有一个Dalvik虚拟机,所以同时开启很多app的时候,因为系统cpu、memory等资源的限制,必然会拖慢整个手机的速度。这也是为什么android跟不上ios速度的原因
在一个解释器上执行VM指令包含三个步骤:指令分派、访问操作数和执行计算
指令分派负责从内存中读取VM指令,然后跳转到相应的解释器代码指令分派中。Stack-based需要更多的指令。
访问操作数是指读取和写回-源操作数和目的操作数
dex字节码是将多个字节码集中在一起,这样就提高了虚拟机对他的访问速度和io效率。然后VM将字节码解释成机器码。那DVM每次执行app的时候都需要即使转换JIT,而ART(android runtime)机制则不同,第一次安装的时候字节码就转成机器码存在本地。这个过程叫AOT预编译Ahead-Of-Time
当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,然后才会拿去执行
寄存器的本质,是runtime stack最顶端的随机访问区域。如果一个指令集允许对runtime stack的最顶端进行随机访问,这个系统就是基于寄存器的。如果一个指令集只允许对runtime stack的top-most的一两个entry进行访问,就是严格的基于栈的系统。
机器码,完全依附硬件而存在~并且不同硬件由于内嵌指令集不同,即使相同的0 1代码意思也可能是不同的~换句话说,根本不存在跨平台性~比如~不同型号的CPU,你给他个指令10001101,他们可能会解析为不同的结果~
我们知道JAVA是跨平台的,为什么呢?因为他有一个jvm,不论那种硬件,只要你装有jvm,那么他就认识这个JAVA字节码~~~~至于底层的机器码,咱不用管,有jvm搞定,他会把字节码再翻译成所在机器认识的机器码
热修复的原理也是如此:
一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。
// pathclassloader加载apk内部的dex文件,而dexclassloader可以加载外部的jar和dex文件,但是PathClassLoader和DexClassLoader都继承自BaseDexClassLoader。在BaseDexClassLoader中有如下源码:
public Class findClass(Stringname) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz =dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;}}}
return null;}
也就是根据名字查找类名去每个dex查找类
如果一个dex中的某个类,在1.static方法 2.private方法 3.构造函数 4.override方法
中直接引用了其他dex中的类,那么该类不会被打上CLASS_ISPREVERIFIED标志。相反,如果是直接引用了同一个dex中的其他类,它将会被加载器打上标记。所以如果我们要在主app的dex中加载热修复dex中的类,就要避免响应类被打上标记。所以有人想出一个“聪明”的办法,在application的oncreate中加上一句话System.out.println(XXX.class),其中XXX是热修复dex中的类。但是有个问题就是,程序刚启动的时候,热修复patch.dex此时还未加载,所以本app会报错找不到该类.
接入前先了解2个类,因为主流的热修复都是同样的原理,让加载器替换掉部分dex文件
1,PathClassLoader:Android是使用这个类作为其系统类和应用类的加载器。并且对于这个类呢,只能去加载已经安装到Android系统中的apk文件
2,DexClassLoader:该类可以用来加载.jar和.apk类型的文件内部加载classes.dex文件。可以用来执行非安装的程序代码
其实在插件化的开发中,我们也是提供一个apk,让我们的app去加载这个apk,它也是通过DexClassLoader来加载的。一个ClassLoader可以包含多个dex文件(可能是apk,jar等格式),每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。
我们知道,将apk解压之后,会看到一个classes.dex的文件,通过反编译工具最后发现它就是我们工程的code。我们安装好的app中,就是通过DexClassLoader.java类来加载这个dex文件的Dalvik
说了这么多都是为热修复做铺垫。热修复需要花点篇幅来说具体的操作,先附一张流程图吧,下次再写一篇热修复代码层的操作: