一、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 源码目录下 ,

【Android 安全】DEX 加密 ( 多 DEX 加载 | 65535 方法数限制和 MultiDex 配置 | PathClassLoader 类加载源码分析 | DexPathList )_MultiDex

参考 : libcore/dalvik/src/main/java/dalvik/system


常用的 ClassLoader 是 PathClassLoaderDexClassLoader ;

PathClassLoader 只能加载 已安装 apk 的 dex ;

DexClassLoader 可以加载 已安装文件系统 中的 APK , DEX , JAR ;

源码参考 :





三、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 数组中 ;