Android DEX 分包

本文探讨了Android应用因Dex方法数量超过65536导致的问题,并介绍了一种解决方案——Dex分包。文章详细解释了Dex分包的工作原理及其实现步骤。

当一个app的功能越来越复杂,代码量越来越多,也许有一天便会突然遇到下列现象:

  1. 生成的apk在2.3以前的机器无法安装,提示INSTALL_FAILED_DEXOPT

  2. 方法数量过多,编译时出错,提示:

Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536

出现这种问题的原因是:

  1. Android2.3及以前版本用来执行dexopt(用于优化dex文件)的内存只分配了5M

  2. 一个dex文件最多只支持65536个方法。

解决这个问题可以使用dex分包方案

简单来说,其原理是将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中,除了第一个dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以资源的方式放在安装包中,并在Application的onCreate回调中被注入到系统的ClassLoader。因此,对于那些在注入之前已经引用到的类(以及它们所在的jar),必须放入第一个Dex文件中。

这里写图片描述

在Android中,我们采用DexClassLoader来加载未安装的类,实际上实现功能是在BaseDexClassLoader里面,我们看它的源码

private final String originalPath;

/** structured lists of path elements */
private final DexPathList pathList;
final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String APK_SUFFIX = ".apk";

    /** class definition context */
    private final ClassLoader definingContext;

    /** dex元素的列表 */
    private final Element[] dexElements;

    /** list of native library directory elements */
    private final File[] nativeLibraryDirectories;

当我们需要装载一个类的时候,实际上是去遍历dex中的Element数组,然后依次去加载所需要的class文件,直到找到为止。

这样我们的解决方案为:将第二个dex文件放入Element数组中,那么在加载第二个dex包中的类时,应该可以直接找到。

下面看看代码实现:

public class BaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
    }
    public String inject(String libPath){
        //判断是否有BaseDexClassLoader
        boolean hasBaseClassLoader = true;
        try {
            Class.forName("dalvik.system.BaseDexClassLoader");
        } catch (ClassNotFoundException e) {
            hasBaseClassLoader = false;
            e.printStackTrace();
        }
        if (hasBaseClassLoader) {
            //获取PathClassLoader
            PathClassLoader pathClassLoader = (PathClassLoader)getClassLoader();
            //获取DexClassLoader
            DexClassLoader dexClassLoader = new DexClassLoader(libPath, getDir("dex", 0).getAbsolutePath(), libPath,getClassLoader());
            try {
                /*
                    通过反射获取PathClassLoader中的DexPathList中的Element数组(已加载了第一个dex包,由系统加载),
                    以及DexClassLoader中的DexPathList中的Element数组(刚将第二个dex包加载进去),将两个数组合并之后
                    再将其赋值给PathClassLoader的Element数组,到此,注入完毕。
                 */
                Object dexElements = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(dexClassLoader)));
                //获取PathClassLoader中的集合
                Object pathList = getPathList(pathClassLoader);
                //将合并后的dexElements数组赋值给PathClassLoader的Element数组
                setField(pathList, pathList.getClass(), "dexElements", dexElements);
                return "SUCCESS";
            } catch (Throwable e) {
                e.printStackTrace();
                return android.util.Log.getStackTraceString(e);
            }
        }
        return "SUCCESS";
    }
}

但是使用dex分包方案仍然有几个注意点:

  1. 由于第二个dex包是在Application的onCreate中动态注入的,如果dex包过大,会使app的启动速度变慢,因此,在dex分包过程中一定要注意,第二个dex包不宜过大。

  2. 由于上述第一点的限制,假如我们的app越来越臃肿和庞大,往往会采取dex分包方案和插件化方案配合使用,将一些非核心独立功能做成插件加载,核心功能再分包加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值