Multidex详解

Multidex详解

1.使用

1.在项目gralde配置中增加依赖 compile "com.android.support:multidex:1.0.2"
2.在AndroidManifest.xml中声明 application为MultiDexApplication,
  或者在自定义的application中,重写如下方法
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        android.support.multidex.MultiDex.install(this);
    }

2.源码详解

   public static void install(Context context) {
        Log.i("MultiDex", "Installing application");
        if(IS_VM_MULTIDEX_CAPABLE) {// vm支持的不需要使用multidex库
            Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
        } else if(VERSION.SDK_INT < 4) {// 太早Android版本不支持的
            throw new RuntimeException("MultiDex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
        } else {
            try {
                //获取安装的apk的信息
                ApplicationInfo applicationInfo = getApplicationInfo(context);
                if(applicationInfo == null) {
                    Log.i("MultiDex", "No ApplicationInfo available, i.e. running on a test Context: MultiDex support library is disabled.");
                    return;
                }
                // 开始执行加载其他dex, 加载到data/data/包名/code_cache/secondary-dexes/
                doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes", "");
            } catch (Exception var2) {
                Log.e("MultiDex", "MultiDex installation failure", var2);
                throw new RuntimeException("MultiDex installation failed (" + var2.getMessage() + ").");
            }

            Log.i("MultiDex", "install done");
        }
    }
    private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix) throws                 IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
        Set var5 = installedApk;// 已经加载不再重新加载
        synchronized(installedApk) {
            if(!installedApk.contains(sourceApk)) {
                installedApk.add(sourceApk);
               ....
                ClassLoader loader;
                try {
                    loader = mainContext.getClassLoader();
                } catch (RuntimeException var11) {
                    Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", var11);
                    return;
                }

                if(loader == null) {
                    Log.e("MultiDex", "Context class loader is null. Must be running in test mode. Skip patching.");
                } else {
                    try {
                        clearOldDexDir(mainContext);
                    } catch (Throwable var10) {
                        Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", var10);
                    }
                    // 获取存放目录
                    File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
                    // 加载dex
                    List<? extends File> files = MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false);
                    // 合并使用dex
                    installSecondaryDexes(loader, dexDir, files);
                }
            }
        }
    }

3.加载过程

    static List<? extends File> load(Context context, File sourceApk, File dexDir, String prefsKeyPrefix, boolean forceReload) throws IOException {
        ...
            // 是否强制重新加载或dex文件被修改了, 以apk的最后修改时间和zip的crc来校验apk是否被修改
             if(!forceReload && !isModified(context, sourceApk, currentCrc, prefsKeyPrefix)) {
                try {
                    files = loadExistingExtractions(context, sourceApk, dexDir, prefsKeyPrefix);
                } catch (IOException var21) {
                    Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var21);
                    files = performExtractions(sourceApk, dexDir);
                    putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, files);
                }
            } else {
                Log.i("MultiDex", "Detected that extraction must be performed.");
                // 准备dex
                files = performExtractions(sourceApk, dexDir);
                // 保存dex的信息(最后时间和crc信息)到multidex.version的sp配置中
                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, files);
            }
        ...
    }
    private static List<ExtractedDex> performExtractions(File sourceApk, File dexDir) throws IOException {
            ....
           int secondaryNumber = 2;
            // 从apk中找出dex文件
            for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" +secondaryNumber + ".dex")) {
                String fileName = extractedFilePrefix + secondaryNumber + ".zip";// 创建dex对应的zip文件
                ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName);
                files.add(extractedFile);
                Log.i("MultiDex", "Extraction is needed for file " + extractedFile);
                int numAttempts = 0;
                boolean isExtractionSuccessful = false;

                while(numAttempts < 3 && !isExtractionSuccessful) {
                    ++numAttempts;
                    // 把dex文件提取出来放到对应的zip文件中, 重试3次
                    extract(apk, dexFile, extractedFile, extractedFilePrefix);

                    try {
                        extractedFile.crc = getZipCrc(extractedFile);
                        isExtractionSuccessful = true;
                    } catch (IOException var19) {
                        isExtractionSuccessful = false;
                        Log.w("MultiDex", "Failed to read crc from " + extractedFile.getAbsolutePath(), var19);
                    }

                    Log.i("MultiDex", "Extraction " + (isExtractionSuccessful?"succeeded":"failed") + " - length " + extractedFile.getAbsolutePath() + ": " + extractedFile.length() + " - crc: " + extractedFile.crc);
                    if(!isExtractionSuccessful) {
                        extractedFile.delete();
                        if(extractedFile.exists()) {
                            Log.w("MultiDex", "Failed to delete corrupted secondary dex '" + extractedFile.getPath() + "'");
                        }
                    }
                }

                if(!isExtractionSuccessful) {
                    throw new IOException("Could not create zip file " + extractedFile.getAbsolutePath() + " for secondary dex (" + secondaryNumber + ")");
                }

                ++secondaryNumber;
            }
        ...
    }

    private static void putStoredApkInfo(Context context, String keyPrefix, long timeStamp, long crc, List<ExtractedDex> extractedDexes) {
        SharedPreferences prefs = getMultiDexPreferences(context);
        Editor edit = prefs.edit();
        edit.putLong(keyPrefix + "timestamp", timeStamp);
        edit.putLong(keyPrefix + "crc", crc);
        edit.putInt(keyPrefix + "dex.number", extractedDexes.size() + 1);
        int extractedDexId = 2;

        for(Iterator var10 = extractedDexes.iterator(); var10.hasNext(); ++extractedDexId) {
            ExtractedDex dex = (ExtractedDex)var10.next();
            edit.putLong(keyPrefix + "dex.crc." + extractedDexId, dex.crc);
            edit.putLong(keyPrefix + "dex.time." + extractedDexId, dex.lastModified());
        }

        edit.commit();
    }

4.合并dex

    private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files) throws IllegalArgumentException,     IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
        if(!files.isEmpty()) {
            if(VERSION.SDK_INT >= 19) {
                V19.install(loader, files, dexDir);
            } else if(VERSION.SDK_INT >= 14) {
                V14.install(loader, files, dexDir);
            } else {
                V4.install(loader, files);
            }
        }

    }
    这里先说明下原理:
    //dalvik.system.DexPathList android遍历dexElements的来加载类的, 所以只要把dex的dexElements合并到apk的pathClassLoade的dexElements中就可以加载到dex中的类
    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;
    }

    private static final class V19 {
        private V19() {
        }

        private static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws    IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
                Field pathListField = MultiDex.findField(loader, "pathList");
                Object dexPathList = pathListField.get(loader);
                ArrayList<IOException> suppressedExceptions = new ArrayList();
                合并dex的dexElements到pathClassLoade的dexElements中, 通过反射实现
                MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
            ...
        }
        // 构建dex的DexElements, 在Android6.0之后的DexPathList没有makeDexElements这个方法, 这就是前面判断vm自身支持multidex,不需要再使用multidex库了
        private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException>  suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", new Class[]{ArrayList.class, File.class, ArrayList.class});
                return (Object[])((Object[])makeDexElements.invoke(dexPathList, new Object[]{files, optimizedDirectory, suppressedExceptions}));
        }
    }

猜你喜欢

转载自blog.csdn.net/a90123/article/details/78110978
今日推荐