MultiDex 源码分析

      Multidex 是为了解决应用程序函数数超过65k而出现的,主要是通过打包时把函数拆分到多个 dex 文件,并在程序启动的时候再动态的读入的方式来解决问题, 详细的描述和 Multidex 的使用方式可以参考官网的这篇文章 https://developer.android.com/tools/building/multidex.html

      本文从源码的角度看看 Multidex 究竟做了什么,  整个项目只有 4 个 java 文件。


使用 Multidex 的时候, 只需要直接继承 MultiDexApplication :
public class MultiDexApplication extends Application {
  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
  }
}
在 attachBaseContext 中直接调用了 MultiDex.install。   attachBaseContext 这个函数, 是在应用程序启动过程中最早被系统触发到的可以做点事情的回调, MultiDex 的逻辑写在这里, 才能保证在应用程序启动之前把所有的 dex 全部读入,保证程序完整。

ZipUtil 里面是几个 crc32 校验的工具函数, 不是重点, 略过、


      那重点就是 MultiDex.java 和 MultiDexExtractor.java 这两个文件。

      MultiDex.install 首先是做了 两件事情, 一个是检查你当前的虚拟机是不是原生就支持 MultiDex , 如果自带支持的就直接返回了,虚拟机自己搞定; 另外是检查系统版本,小于 4 的不支持, 直接返回。 大于20  会抛个警告给你看看, 但是程序还接着跑。

注:有的童鞋可能不太清楚,Android 4.4 引入了 ART 运行时,而 ART 原生就是支持 MultiDex 的, 所以这里先判断一下设备是不是 ART 运行时,如果不是才有必要执行后续的程序。  至于如何判断出来当前的运行时版本, 官网有这么一段说明:
You can verify which runtime is in use by calling System.getProperty("java.vm.version") . If ART is in use, the property's value is  "2.0.0"  or higher.  


     一通检查过后,来到了 MultiDex 的核心代码:
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
if (checkValidZipFiles(files)) {
    installSecondaryDexes(loader, dexDir, files);
} else {
    Log.w(TAG, "Files were not valid zip files.  Forcing a reload.");
    // Try again, but this time force a reload of the zip file.
    files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);

    if (checkValidZipFiles(files)) {
        installSecondaryDexes(loader, dexDir, files);
    } else {
        // Second time didn't work, give up
        throw new RuntimeException("Zip files were not valid.");
    }
}
做了两件事:
(1) 借助 MultiDexExtractor.load 读取了一个 List<File>
(2) 把上面获取到的 List<File>, 连同当前使用的 ClassLoader ,一起传入 installSecondaryDexes

下面就分别看下 这两步核心的代码:
一、MultiDexExtractor.load
 
  
List<File> files;
if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
    try {
        files = loadExistingExtractions(context, sourceApk, dexDir);
    } catch (IOException ioe) {
        Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
                + " falling back to fresh extraction", ioe);
        files = performExtractions(sourceApk, dexDir);
        putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);

    }
} else {
    Log.i(TAG, "Detected that extraction must be performed.");
    files = performExtractions(sourceApk, dexDir);
    putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}

这里是两个分支, 一个是读取已经解压出来的包含 dex 的压缩文件-loadExistingExtractions ,另一个是现在去解压 - performExtractions 
(1) performExtractions 
直接把 apk 文件作为 ZipFile 打开, 遍历找到里面名字是 “classesX.dex” ( X = 数字 ) 的文件 , 重新创建一个 Zip 压缩包, 并把这个 dex 文件改名为 ”classes.dex” 再存到新的 zip 包里, 就是把名字里面的数字扣掉了, 同时, 新创建 出来的, 只包含一个 classes.dex 文件的压缩包, 会被添加到一个 List<File> 里面, 最后返回。

(2)loadExistingExtractions 
这个就简单多了, 直接去指定的目录里面, 把已经解压好的、只包含一个 classes.dex 文件的 zip 纪录到一个 List<File> 中, 然后返回。

到这里, 第一部 load 就完成了, 现在拿到了一个 List<File> 集合, 每一个 File 是 一个 zip 包,包中只有一个从当前 apk 文件中提取出来的 classes.dex 文件。 


二、installSecondaryDexes
这个代码结构在 Android 源码的 XxxCompat 中经常出现,很经典, 一定要贴出来看看、、
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)
        throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
        InvocationTargetException, NoSuchMethodException, IOException {
    if (!files.isEmpty()) {
        if (Build.VERSION.SDK_INT >= 19) {
            V19.install(loader, files, dexDir);
        } else if (Build.VERSION.SDK_INT >= 14) {
            V14.install(loader, files, dexDir);
        } else {
            V4.install(loader, files);
        }
    }
}

这里就是最后执行 install 操作的入口了, 从代码上就能看出来, 在 api 14 和 19 这两个版本中,官方对 dex 加载的地方做了一些调整, 所以根据不同的版本做分别实现,挑个老的下手吧,看下 V4.install 

V4.install, 函数很简单, 总共没几行我就全部贴出来了

private static final class V4 {
    private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
                    throws IllegalArgumentException, IllegalAccessException,
                    NoSuchFieldException, IOException {
        /* The patched class loader is expected to be a descendant of
         * dalvik.system.DexClassLoader. We modify its
         * fields mPaths, mFiles, mZips and mDexs to append additional DEX
         * file entries.
         */
        int extraSize = additionalClassPathEntries.size();

        Field pathField = findField(loader, "path");

        StringBuilder path = new StringBuilder((String) pathField.get(loader));
        String[] extraPaths = new String[extraSize];
        File[] extraFiles = new File[extraSize];
        ZipFile[] extraZips = new ZipFile[extraSize];
        DexFile[] extraDexs = new DexFile[extraSize];
        for (ListIterator<File> iterator = additionalClassPathEntries.listIterator();
                iterator.hasNext();) {
            File additionalEntry = iterator.next();
            String entryPath = additionalEntry.getAbsolutePath();
            path.append(':').append(entryPath);
            int index = iterator.previousIndex();
            extraPaths[index] = entryPath;
            extraFiles[index] = additionalEntry;
            extraZips[index] = new ZipFile(additionalEntry);
            extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0);
        }

        pathField.set(loader, path.toString());
        expandFieldArray(loader, "mPaths", extraPaths);
        expandFieldArray(loader, "mFiles", extraFiles);
        expandFieldArray(loader, "mZips", extraZips);
        expandFieldArray(loader, "mDexs", extraDexs);
    }
}

可以看到这里只干了一件事, 通过反射, 拿到 classloader 中的 “path” 属性, 然后把上一步 拿到的 List<File> 中 File 的路径全部拼到”path”里面, 中间用 冒号分割。
“path” 里面添加了内容以后,相应的 “mPaths”, “mFiles”, “mZips” 和 “mDexs” 这几个数组属性的内容也要与 “path”对应, expandFieldArray 这个函数就是通过反射 获取上述几个属性, 构造出所需的数据结构, 然后塞到各个 属性数组中,到这里 MultiDex 的所有工作就结束了。

注: V14  和  V19 中做的事情跟 V4 都差不多, 就是属性变了变名字, 构造数组直接使用了新版本中添加的函数,这里就不再赘述了。


总结
     一个首次安装的程序启动时,MultiDex 的整个工作过程, 概括起来就是两句话:
1、把 apk 中的 classesX.dex 文件解压出来, 单独压缩到独立的 zip 压缩包中, 并把这些压缩包的路径包装在一个 List 中返回。
2、通过反射, 修改 classloader 中的 “path” 属性(新版中是“pathList”),把上一步的到的所有 zip 的路径拼进去。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值