美文网首页
MultiDex原理

MultiDex原理

作者: None_Ling | 来源:发表于2020-09-14 19:23 被阅读0次

简述

MultiDex适用于API版本在4-20的Android系统 , 即Android 2.1 - 4.4 . 而在这些版本之间 , MultiDex会通过Application.getClassLoader进行加载. 而如果Dex比较多比较大的话 , 主线程加载Dex时间会很长 , 导致主线程ANR.

由于Android 5.0之后使用ART虚拟机进行dex2oat , 将多dex在安装的时候将APK中多个Dex进行优化 , 优化过后生成一个ELF文件 , 名为.oat文件. 在加载后 , 会将oat文件直接映射到ART虚拟机中使用 , 这样就减少Dex加载的耗时.

MultiDex加载过程简述

在加载过程中 :

  • 读取APK的CRC32以及modifyTime进行校验
  • 通过反射 , 从BaseDexClassLoader中找到pathList对象
  • 通过反射调用PathList.makeDexElements创建Elements[]
  • 通过反射将Elements[]添加到dexElements数组中
  • 后续在该ClassLoader查找类到时候 , 会优先在dexElements中开始遍历查找

MultiDex加载过程

  1. 调用install函数进行加载
  • 获取ApplicationInfo , 以及data、source目录路径
  • 调用doInstallation加载Dex
public static void install(Context context) {
        ...
        // 如果当前版本小于4 , 不支持
        if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
            throw new RuntimeException("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT
                    + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
        }

        try {
            // 获取Application的ApplicationInfo
            ApplicationInfo applicationInfo = getApplicationInfo(context);
            if (applicationInfo == null) {
              Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:"
                  + " MultiDex support library is disabled.");
              return;
            }
            // 传入/data/data路径以及代码路径
            doInstallation(context,
                    new File(applicationInfo.sourceDir),
                    new File(applicationInfo.dataDir),
                    CODE_CACHE_SECONDARY_FOLDER_NAME,
                    NO_KEY_PREFIX,
                    true);

        } catch (Exception e) {
            Log.e(TAG, "MultiDex installation failure", e);
            throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ").");
        }
        Log.i(TAG, "install done");
    }
  1. doInstallation
  • 检查版本
  • 从Application中获取DexClassLoader
  • 清理secondary-dexes文件夹
  • 创建MultiDexExtractor用于读取APK中的文件
  • 调用installSecondaryDexes开始安装classes2.dex后的Dex文件
 private static void doInstallation(Context mainContext, File sourceApk, File dataDir,
            String secondaryFolderName, String prefsKeyPrefix,
            boolean reinstallOnPatchRecoverableException)  {
        synchronized (installedApk) {
            // 如果已经加载过的APK就直接返回
            if (installedApk.contains(sourceApk)) {
                return;
            }
            installedApk.add(sourceApk);
            // 如果版本高于20 , 也就5.0以上的版本就会提示
            if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
                Log.w(TAG, "MultiDex is not guaranteed to work in SDK version "
                        + Build.VERSION.SDK_INT + ": SDK version higher than "
                        + MAX_SUPPORTED_SDK_VERSION + " should be backed by "
                        + "runtime with built-in multidex capabilty but it's not the "
                        + "case here: java.vm.version=\""
                        + System.getProperty("java.vm.version") + "\"");
            }

            //  从Application中拿到ClassLoader , 并且进行类型校验
            // 判断是否为BaseDexClassLoader还是PathClassLoader
            ClassLoader loader = getDexClassloader(mainContext);
            if (loader == null) {
                return;
            }
            // 清理secondary-dexes目录
            try {
              clearOldDexDir(mainContext);
            } catch (Throwable t) {
              Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
                  + "continuing without cleaning.", t);
            }
            // 得到/data/data/pkg/secondary-dexes目录
            File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
            // 创建MultiDexExtractor , 即MultiDex提取器
            MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
            IOException closeException = null;
            try {
                // 通过extractor加载Dex
                List<? extends File> files =
                        extractor.load(mainContext, prefsKeyPrefix, false);
                try {
                    // 安装Dex文件
                    installSecondaryDexes(loader, dexDir, files);
                // Some IOException causes may be fixed by a clean extraction.
                } catch (IOException e) {
                    if (!reinstallOnPatchRecoverableException) {
                        throw e;
                    }
                    Log.w(TAG, "Failed to install extracted secondary dex files, retrying with "
                            + "forced extraction", e);
                    files = extractor.load(mainContext, prefsKeyPrefix, true);
                    installSecondaryDexes(loader, dexDir, files);
                }
            } finally {
                try {
                    extractor.close();
                } catch (IOException e) {
                    // Delay throw of close exception to ensure we don't override some exception
                    // thrown during the try block.
                    closeException = e;
                }
            }
            if (closeException != null) {
                throw closeException;
            }
        }
    }
  1. MultiDexExtractor
  • 获取multidex.lock文件 , 用于文件锁
  • 锁住文件
MultiDexExtractor(File sourceApk, File dexDir) throws IOException {
        Log.i(TAG, "MultiDexExtractor(" + sourceApk.getPath() + ", " + dexDir.getPath() + ")");
        this.sourceApk = sourceApk;
        this.dexDir = dexDir;
        // 获取APK的CRC
        sourceCrc = getZipCrc(sourceApk);
        File lockFile = new File(dexDir, LOCK_FILENAME);
        // 拿到Lock文件的文件锁
        lockRaf = new RandomAccessFile(lockFile, "rw");
        try {
            lockChannel = lockRaf.getChannel();
            try {
                Log.i(TAG, "Blocking on lock " + lockFile.getPath());
                // 锁住File Channel
                cacheLock = lockChannel.lock();
            } catch (IOException | RuntimeException | Error e) {
                closeQuietly(lockChannel);
                throw e;
            }
            Log.i(TAG, lockFile.getPath() + " locked");
        } catch (IOException | RuntimeException | Error e) {
            closeQuietly(lockRaf);
            throw e;
        }
    }
  1. load
  • 检查文件锁是否失效
  • 检查APK的CRC、ModifyTime是否与之前的APK CRC、ModifyTime一样
  • 如果不一样 , 则调用performExtractions来清理之前的无效文件以及读取Dex
  • 将最新的Dex的CRC与ModifyTime保存到SP中
List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload)
            throws IOException {
        Log.i(TAG, "MultiDexExtractor.load(" + sourceApk.getPath() + ", " + forceReload + ", " +
                prefsKeyPrefix + ")");
        // 检查文件锁是否失效 
        if (!cacheLock.isValid()) {
            throw new IllegalStateException("MultiDexExtractor was closed");
        }

        List<ExtractedDex> files;
        // forceReload为false , 判断文件的modify时间以及CRC是否相同
        if (!forceReload && !isModified(context, sourceApk, sourceCrc, prefsKeyPrefix)) {
            // 本次文件的CRC与modify time与之前的Dex文件相同的话 , 就会来加载
            try {
                files = loadExistingExtractions(context, prefsKeyPrefix);
            } catch (IOException ioe) {
                Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
                        + " falling back to fresh extraction", ioe);
                files = performExtractions();
                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc,
                        files);
            }
        } else {
            if (forceReload) {
                Log.i(TAG, "Forced extraction must be performed.");
            } else {
                Log.i(TAG, "Detected that extraction must be performed.");
            }
            // 如果CRC与之前SP中保存不一致的话 , 就会通过这个方法查找dex文件个数
            files = performExtractions();
            putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc,
                    files);
        }

        Log.i(TAG, "load found " + files.size() + " secondary dex files");
        return files;
    }
  1. performExtractions
  • 清理secondary-classes文件夹
  • classes2.dex开始从APK中读取Dex文件
  • 将DexFile写入到本地临时文件中
  • 计算文件CRC
  • 将所有Dex的CRC保存到List中返回
private List<ExtractedDex> performExtractions() throws IOException {
        // 得到xxx.apk.classes
        final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
        // 清理secondary-classes文件夹
        clearDexDir();
        List<ExtractedDex> files = new ArrayList<ExtractedDex>();
        // 创建ZipFile
        final ZipFile apk = new ZipFile(sourceApk);
        try {
            int secondaryNumber = 2;
            // 得到classes2.dex文件, 因为classes.dex已经加载
            ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
            while (dexFile != null) {
                // 找到dex的文件名
                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
                // 创建DexFile路径文件
                ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName);
                files.add(extractedFile);
                int numAttempts = 0;
                boolean isExtractionSuccessful = false;
                // 每个Dex尝试最多尝试读取3次
                while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
                    numAttempts++;
                    // 创建Dex临时文件 , 也就是把Zip里的文件读取后 , 写入一个临时文件
                    extract(apk, dexFile, extractedFile, extractedFilePrefix);
                    // 计算临时文件的CRC32
                    try {
                        extractedFile.crc = getZipCrc(extractedFile);
                        // 计算成功
                        isExtractionSuccessful = true;
                    } catch (IOException e) {
                        isExtractionSuccessful = false;
                        Log.w(TAG, "Failed to read crc from " + extractedFile.getAbsolutePath(), e);
                    }
                    if (!isExtractionSuccessful) {
                        // 如果提取失败 , 删除临时文件
                        extractedFile.delete();
                        if (extractedFile.exists()) {
                            Log.w(TAG, "Failed to delete corrupted secondary dex '" +
                                    extractedFile.getPath() + "'");
                        }
                    }
                }
                // 如果提取3次失败 , 抛异常
                if (!isExtractionSuccessful) {
                    throw new IOException("Could not create zip file " +
                            extractedFile.getAbsolutePath() + " for secondary dex (" +
                            secondaryNumber + ")");
                }
                // 开始读取下一个classes3.dex文件
                secondaryNumber++;
                // 获取下一个dexFile
                dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
            }
        } finally {
            try {
                apk.close();
            } catch (IOException e) {
                Log.w(TAG, "Failed to close resource", e);
            }
        }
        return files;
    }
  1. putStoredApkInfo
  • 将CRC更新到SharedPreferences中
  • 使用commit写入
private static void putStoredApkInfo(Context context, String keyPrefix, long timeStamp,
            long crc, List<ExtractedDex> extractedDexes) {
        
        SharedPreferences prefs = getMultiDexPreferences(context);
        SharedPreferences.Editor edit = prefs.edit();
        edit.putLong(keyPrefix + KEY_TIME_STAMP, timeStamp);
        // 将APK文件的CRC保存到SP中
        edit.putLong(keyPrefix + KEY_CRC, crc);
        edit.putInt(keyPrefix + KEY_DEX_NUMBER, extractedDexes.size() + 1);

        int extractedDexId = 2;
        // 保存从classes2.dex后的CRC以及时间
        for (ExtractedDex dex : extractedDexes) {
            edit.putLong(keyPrefix + KEY_DEX_CRC + extractedDexId, dex.crc);
            edit.putLong(keyPrefix + KEY_DEX_TIME + extractedDexId, dex.lastModified());
            extractedDexId++;
        }
        // 需要一个同步写入的方法
        edit.commit();
    }

7.installSecondaryDexes开始加载Dex

  • 根据当前Android版本进行安装
 private static void installSecondaryDexes(ClassLoader loader, File dexDir,
        List<? extends File> files)
            throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
            InvocationTargetException, NoSuchMethodException, IOException, SecurityException,
            ClassNotFoundException, InstantiationException {
        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);
            } else {
                V4.install(loader, files);
            }
        }
    }
  1. install
  • V19版本中, 通过pathList对象到makeDexList创建Elements
  • 合并到主Dex中的Elements中
static void install(ClassLoader loader,
                List<? extends File> additionalClassPathEntries,
                File optimizedDirectory)
                        throws IllegalArgumentException, IllegalAccessException,
                        NoSuchFieldException, InvocationTargetException, NoSuchMethodException,
                        IOException {
            // 从PathClassLaoder中读取pathList属性
            Field pathListField = findField(loader, "pathList");
            // 获取pathList对象
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            // 调用makeDexElements , 并且添加到dexPathList对象中
            expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
                    new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
                    suppressedExceptions));
            ...
        }
        private static Object[] makeDexElements(
                Object dexPathList, ArrayList<File> files, File optimizedDirectory,
                ArrayList<IOException> suppressedExceptions)
                        throws IllegalAccessException, InvocationTargetException,
                        NoSuchMethodException {
            // 调用pathList.makeDexElements生成Elements对象
            Method makeDexElements =
                    findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
                            ArrayList.class);

            return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
                    suppressedExceptions);
        }

相关文章

  • 热修复实现原理(一)

    热修复实现原理——MultiDex 一、 MultiDex 1、MultiDex 产生背景 当Android系统安...

  • MultiDex原理

    简述 MultiDex适用于API版本在4-20的Android系统 , 即Android 2.1 - 4.4 ....

  • 收集_开源框架

    一、MultiDex: Multidex(一)之源码解析Multidex(二)之Dex预加载优化MultiDex(...

  • Multidex

    Multidex 这个插件相信大家使用的不少,今天说下他的原理,以及最近遇到的一个问题。 实现原理 实现原理是将c...

  • Android MultiDex实现原理解析

    本文主要从源码角度出发,分析MultiDex的实现原理。 出处: Allen's Zone作者: Allen Fe...

  • Android multidex 使用 与 实现原理

    Android multidex 使用 与 实现原理 在Android中一个Dex文件最多存储65536个方法,也...

  • Bug百科之Android开发篇

    一、棘手问题 1.Dex 65k 原理分析Android MultiDex实践:如何绕过那些坑?http://bl...

  • MutliDex流程梳理

    流程图从MultiDex.install(context)说起: V19通过反射更新数据原理: http://an...

  • Android MultiDex工作原理分析和优化方案

    第134期:Android MultiDex工作原理分析和优化方案 一点推荐 1、即刻 App “追踪机器人” 初...

  • 热修复相关

    业界热修复流派 基于MultiDex的Dex注入代表:Tinker,手Q空间,Nuwa原理:将补丁Dex对应的De...

网友评论

      本文标题:MultiDex原理

      本文链接:https://www.haomeiwen.com/subject/rikeektx.html