MultiDex

因此,分包库只针对Dalvik, 即5.0以下

  • 默认情况下,Dalvik 限制应用的每个 APK 只能使用单个 classes.dex 字节码文件,
    • 将/data/app/apkName.apk路径下解压得到的classes2.dex, …, classesN.dex,依次写入到/data/data/pkgName/code_cache/secondary-dexes/apkName.apk.classes2.zip等zip文件的classes.dex中,并返回这个zip列表
    • 将这个要安装的zip列表加入BaseDexClassLoader的pathList实例的dexElements数组中,其中会针对各dex文件进行dex2opt优化
    • 一旦加入到了dexElements数组中,程序启动的时候,ClassLoader会加载dexElements数组中的元素,从而实现multi dex的安装
  • Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,后者原生支持从 APK 文件加载多个 DEX 文件。
    • ART 在应用安装时执行预编译,扫描 classesN.dex 文件,并将它们编译成一个 .oat 文件,供 Android 设备执行。
    • 因此,minSdkVersion 为 21 或更高值,则不需要 Dalvik 可执行文件分包支持库。

简而言之就是在dalvik,多个dex会通过multidex将各个dex加入dexElements中并且进行dexopt, 在art是多个dex编译成一个oat文件

Android Build System

understanding-multidesk-build-process.png

  • compile source code to Dex
  • APK packageer combines Dex files and compiled resources into a single APK
  • APK Packager sings APK
  • Before generating the final APK, optimze the code to avoid the unused code

image.png

Dex文件

  • Dex文件是Android平台上和传统Class文件对应的Java字节码文件
  • Dex文件核心内容和传统文件类似,只不过对移动设备做了一些定制化处理
    • ARM cpu显著特点:通用寄存器比较多

android安装apk过程

  • Android系统通过PackageManagerService来安装APK,在安装的过程,PackageManagerService会通过另外一个类Installer的成员函数dexopt来对APK里面的dex字节码进行优化:
    • Installer通过socket向守护进程installd发送一个dexopt请求,这个请求是由installd里面的函数dexopt来处理的。
    • 但是!!android系统它只会将主dex编译成odex,不能将子dex也变成odex加载进内存中
    • 在应用启动的回调方法中,将其他的子dex文件手动解压、编译、加载进内存中—google官方做法

从安装过程上来看
Java的代码实际上需要两次“转换”才可以在android设备上运行
一.PC端:.class->.dex->.apk
二.phone:dex->odex
区别在于第二步。
ART : .dex->.odex(机器码)(AOT  Ahead-Of-Time)
Dalvik: .dex->.odex(字节码)(JIT Just-In-Time)

  • 执行 ODEX 文件的效率会比直接执行 Dex 文件的效率要高很多

机器码可直接执行,而字节码每次启动都需要执行将优化过的odex字节码再转换成机器码

dexopt

oat, It’s Of Ahead Time, a silly reordering of Ahead Of Time.

info dexopt dex2oat
compile JIT AOT
output odex (optimized dex) file 生成物每个版本不同
OAT file(.oat, may contain machine code in the ELF format.)文件名可能是dex或者odex,但是其实是oat文件
Android O有三个:
- .vdex: contains the uncompressed DEX code of the APK, with some additional metadata to speed up verification.
- .odex: contains AOT compiled code for methods in the APK.
- .art (optional): contains ART internal representations of some strings and classes listed in the APK, used to speed application startup.
optimized replacing a virtual invoke instruction with an optimized version that includes the vtable index of the method being called 直接转机器码

Both tools are normally run at install time on the device.

image.png

dex file

逻辑上,可以把dex文件分成3个区,头文件、索引区和数据区。索引区的ids后缀为identifiers的缩写。
image.png

odex file

[ODEX格式及生成过程]link

  • Apk在安装(installer)时,就会进行验证和优化,目的是为了校验代码合法性及优化代码执行速度
  • 验证和优化后,会产生ODEX文件,运行Apk的时候,直接加载ODEX,避免重复验证和优化,加快了Apk的响应时间。

odex-file-structure.webp

  • 一. 首先将一个空的DexOptHeader写入ODEX文件
  • 二. 从Apk中提取classes.dex,追加到ODEX文件
  • 三. 修改Dex内容
  • 四. 因为3修改了Dex内容,所以要重新计算Dex的checksum
  • 五. 往ODEX文件后面追加Dependenices内容(是指Dex文件之间的依赖)
  • 六. 将优化的其他内容追加到ODEX文件
  • 七. 根据所有的内容,改写第一步中DexOptHeader的相关字段值。

oat文件

  • OAT文件本质上是一个ELF文件,因此在最外层它具有一般ELF文件的结构,例如它有标准的ELF文件头以及通过段(Section)来描述文件内容。
  • OAT文件包含有两个特殊的段oatdata和oatexec,前者包含有用来生成本地机器指令的dex文件内容,后者包含有生成的本地机器指令,它们之间的关系通过储存在oatdata段前面的oat头部描述。

64K方法数Dex分包优化方案
Android中dex文件的加载与优化流程
dex2oat的原理及慢的原因

why 65536

  • 早期的Android系统中,DexOpt有一个问题,DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。
    • invoke-kind (调用各类方法)指令中,方法引用索引数是 16 位的,也就是最多调用 2^16 = 65536 个方法,这就是 DexFormatMAX_MEMBER_IDX 为 0xFFFF 的原因。
    • 其实本质是dalvik指令的限制
  • 新版本的Android系统中,DexOpt修复了这个问题,但是我们仍然需要对低版本的Android系统做兼容

multidex工作流程

MultiDex的工作流程具体分为两个部分

  • 一个部分是打包构建Apk的时候,将Dex文件拆分成若干个小的Dex文件,这个Android Studio已经帮我们做了(设置 “multiDexEnabled true”)
  • 另一部分就是在启动Apk的时候,同时加载多个Dex文件(具体是加载Dex文件优化后的Odex文件,不过文件名还是.dex),这一部分工作从Android 5.0开始系统已经帮我们做了,但是在Android 5.0以前还是需要通过MultiDex Support库来支持(MultiDex.install(Context))。

multidex

  • before Android 5.0 (API level 21) —you can’t use more than one bytecode file per APK.
  • Android version 5.0 (API level 21) or higher. uses a runtime called ART which supports loading multiple DEX files from APK files
  1. If you do not override the Application class
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.myapp">
  4. <application
  5. android:name="android.support.multidex.MultiDexApplication" >
  6. ...
  7. </application>
  8. </manifest>
  1. If you do override the Application class
  1. public class MyApplication extends MultiDexApplication { ... }
  1. Or if you do override the Application class but it’s not possible to change the base class[也可以异步线程优化]
  1. public class MyApplication extends SomeOtherApplication {
  2. @Override
  3. protected void attachBaseContext(Context base) {
  4. super.attachBaseContext(base);
  5. MultiDex.install(this);
  6. }
  7. }

multidex 安装过程

分析MultiDex的的入口就是它的静态方法install()。 这个方法的作用就是把从应用的APK文件中的dex添加到应用的类加载器PathClassLoader中的DexPathList的Emlement数组中。

  • 通过一定的方式把dex文件抽取出来,然后把这些dex文件追加到DexPathList的Element[]数组的后面,这个过程要尽可能的早,所以一般是在Application的attachBaseContext()方法中。
  • 一些热修复技术,就是通过一定的方式把修复后的dex插入到DexPathList的Element[]数组前面,实现了修复后的class抢先加载。