#简介:
之前介绍了Dex分包原理和65535原因,apk 分包官网给了详细的步骤。这里来说下分包过程的 compile 的 support-multidex 包的原理。
Support-multidex原理
首先集成 support-multidex,不多赘述。
分析的是 support-multidex-1.03
,里面类不多,其实一共两个关键类:MultiDex
和 MultiDexxtractor
,调用入口肯定是 MultiDex.install
,install
方法判断了是否符合 multidex 执行条件,如:虚拟机版本号和 SDK 版本号之类;其中会判断 Dalvik 虚拟机版本是否支持内置加载 Mulitdex 功能,如果虚拟机支持就用虚拟机内置的加载 Mulitdex 功能,然后 disable support-mulitdex library,如果虚拟机不支持 mulitdex 才会启用 support-multidex。
我写了个 Demo 看了下 MultiDex 的 log:
|
|
虚拟机内置加载 multidex 功能应该和 support-multidex 大同小异,我们看下走 support-multidex 情况,判断完版本调用 doInstallation
:
|
|
主要逻辑就是:
- 检测是否需要 support-multidex 工作;
- 需要工作的话,先清理存储 secondary dexes 目录;
- 然后提取 apk secondary dexes 到 指定目录;
- 通过反射加载 secondary dexes
有两处关键逻辑:
- 提取 secondary dexes
- 加载 secondary dexes
先看提取 extractor.load
:
注:CRC的全称是循环冗余校验值:一种检测或者校验数据传输或者数据保存后可能出现错误的 哈希函数值。
|
|
提取 secondary dexes 的操作:
|
|
理一下提取 secondary dexes 的逻辑:
- 判断 apk 是否被修改,没有的话,加载之前提取的内容;
performExtractions
解压 apk,把apk中的 classesN.dex (N>1) 字节提取到 /data/user/0//code_cache/secondary-dexes/base.apk.classesN.dex.zip 当中; putStoredApkInfo
保存本次提取操作信息,方便下次判断;
在看加载 secondary dexes 之前,我们来了解下 Android 中 classLoader 知识。
关于 ClassLoader 有两点要说:
- 类加载流程;
- 双亲委托模型;
类加载详细流程比较复杂,这里简单聊下。加载一个类到内存,首先会通过类的全限定名来获取此类的二进制字节流,但是虚拟机规范并没有规定怎么来获取二进制流,从哪里获取。这也是虚拟机设计团队有意为之,也因此诞生了很多强大的技术,比如:动态代理等。
所以获取二进制字节流的方式,既可以通过虚拟机的引导类加载器由虚拟机来完成加载,也可以通过我们自定义 ClassLoader 来完成。举个场景,比如,因为某些限制条件,需要把 Class 分开来加载,分开的部分可以用自定义的 ClassLoader 来加载到内存。听起来像不像 multiDex 中加载 Secondary dexes 哈哈。
也简单说下双亲委托模型,定义:除了顶层的 Bootstrap ClassLoader(所有 ClassLodaer 的父加载器) 外,所有 ClassLoader 都必须有父加载器;当收到类加载请求的时候,只有父加载器无法加载的时候,子加载器才会加载。
而标识一个 Class 需要这个类本身和加载该类的 ClassLoader 两者来决定。所以我们自定义的 java.lang.Object 和 JDK 中的 Object 不是同一个类,因为 JDK 中的类是通过虚拟机的顶层加载器 Bootstrap ClassLoader 加载的,而这个加载器只会加载特定文件,比如 JDK 中的 rt.jar,所以即便我们自己写个 java.lang.Object 打个 jar 包,丢进 JDK lib 目录也是不会被加载的。 所以在所有 ClassLoader 加载的 class 中 Object 类都是同一个。保证了 Java 程序安全性。
再来看 Android 中的 ClassLoader
|
|
看来是 PathClassLoader
呢。
5.0 Android 源码 中 ClassLoader 的继承关系:
而 PathClassLoader
都继承自 BaseDexClassLoader
|
|
主要类加载功能都在 BaseDexClassLoader
中了,但是他有两个子类 :PathClassLoader
和 DexClassLoader
,我们来看下这两者区别:
PathClassLoader :
|
|
DexClassLoader
|
|
可见DexClassLoader
和PathClassLoader
只有调用父类构造器时候,是否传optimizedDirectory
的区别,跟踪BaseDexClassLoader
中的构造器optimizedDirectory
引用:
|
|
optimizedDirectory
为空,会直接加载 Dex,而不为空的 loadDex
方法中有这么一段注释:
|
|
所以 : PathClassLoader
用于加载系统 class 和已经安装的 apk; DexClassLoader
可以根据传入的 optimizedDirectory
来加载自定义目录里的 dex、jar 等 class。
理一下 Android 的 class 加载过程 :BaseClassLoader
找到 makeDexElements
生成的 dexElements
然后遍历包含的 class 字节的 element, 最终通过 native 方法来加载所有 class 到内存。
再看加载 installSecondaryDexes
:
|
|
做了SDK Version 适配,原理一样,看一个分支即可,选择跟踪MultiDex.V19.install(loader, files, dexDir)
:
|
|
最后来理一下 Multidex 加载原理 :
- 判断 Davlik 版本,是否支持 内置 multidex 功能;和 SDK 版本是否支持 support-multidex;
- 实例化 ClassLoader ,确保 secondary dexes 目录存在并清理;
- MultidexExtractor 解压 apk 提取 classesN.dex 的字节到 /data/user/0/
/code_cache/secondary-dexes/base.apk/classesN.dex.zip 中;每个 zip 中都只有一个 名为 classes.dex 的 entry; - 把当前 apk crc 校验值和 sharepreference 中之前保存的做比较,
- 如果相等,就直接用上次的提取过的 files;
- 如果不等,就重新执行提取操作,并保存本次 lastModified time 和 crc;
- 安装 secondary dexes :通过发射获取
BaseClassLoader
中DexpathList
中的Element[]
用包含 secondary dexes 的 新 Element[] 替换旧的。