文章目录
一、65535 方法数限制和 MultiDex 配置
在 Android 开发中 , 尤其是项目比较大时 , 或引入的依赖库过多 , 一般的项目后期都会遇到 如下问题 , 整个工程的方法数超过了 65535 65535 65535 个 , 编译时报如下错误 :
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
Google 官方给出了解决方案 : https://developer.android.google.cn/studio/build/multidex.html
5.0 5.0 5.0 之前的版本 , 应用中只能使用单个 classes.dex , 5.0 5.0 5.0 及以后的版本 原生支持多个 classes.dex 文件 ;
如果最低版本号大于等于 21 21 21 , 就可以不用配置 MultiDex ;
反之版本号小于 21 21 21 , 则 必须进行 MultiDex 配置 ;
Google 提供的 com.android.support:multidex 库 , 就是为了兼容 5.0 5.0 5.0 之前的版本 ;
二、多 DEX 加载引入
MultiDexApplication 中就使用 Android 中的类加载机制 , ClassLoader 加载机制 ;
ClassLoader 相关源码在 libcore/dalvik/src/main/java/dalvik/system Android 源码目录下 ,
参考 : libcore/dalvik/src/main/java/dalvik/system
常用的 ClassLoader 是 PathClassLoader 和 DexClassLoader ;
PathClassLoader 只能加载 已安装 apk 的 dex ;
DexClassLoader 可以加载 已安装 或 文件系统 中的 APK , DEX , JAR ;
源码参考 :
- PathClassLoader : libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
- DexClassLoader : libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
三、PathClassLoader 类加载源码分析
在 Android 中使用的是 PathClassLoader , 该类很简单 , 只是单纯的继承 BaseDexClassLoader ,
package dalvik.system;
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
参考源码地址 : libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
四、BaseDexClassLoader 类加载源码分析
BaseDexClassLoader 是通过 Class<?> findClass(String name) 方法查找类的 , 给定一个全类名字符串 , 返回字节码 ,
查找类通过调用 pathList 的 findClass 方法 , 获取字节码文件 ,
pathList 对应的成员变量定义是 DexPathList pathList , 它是 DexPathList 类型的 ,
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
}
参考源码地址 : libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
五、DexPathList 相关载源码分析
DexPathList 的 findClass 方法 , 主要是遍历 Element[] dexElements 成员 ,
Element[] dexElements 数组中保存的就是内存中的 DEX 文件 , 如果 APP 中有 3 3 3 个 DEX 文件 , 那么该数组就有 3 3 3 个元素 ;
然后 获取该 element 中的 dexFile , 这是 DexFile 类型文件 ,
调用 DexFile 的 loadClassBinaryName 加载对应的类 ,
/*package*/ final class DexPathList {
/**
* dex/resource (class path) 元素集合.
* 应该调用 pathElements , 但是 Facebook 应用通过反射修改 dexElements .
*/
private final Element[] dexElements;
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
}
参考源码地址 : libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
六、多 DEX 存放位置
上述 DexPathList 中的 Element[] dexElements 成员就保存了当前应用中的 DEX 文件 ,
再次回到 classes.dex 分割的问题上 , 如果我们设置一个主 DEX , 其中只存在代理 Application , 用于解密其它被加密的 DEX , 其它的 DEX 都是被加密的 ;
这些 DEX 文件 都需要被存放到上述 DexPathList 的 Element[] dexElements 数组中 ;