MultiDex與熱修復(fù)實(shí)現(xiàn)原理(二)MultiDex基本原理及解析

當(dāng)一個(gè)app的功能越來越復(fù)雜果录,代碼量越來越多颠蕴,可以遇到下面兩種情況:

  1. 生成的apk在2.3以前的機(jī)器無法安裝哨免,提示INSTALL_FAILED_DEXOPT
  2. 方法數(shù)量過多亚情,編譯時(shí)出錯(cuò),提示:Conversion to Dalvik format failed:Unable to execute dex: method IDnot in [0, 0xffff]: 65536

原因:

  1. Android2.3及以前版本用來執(zhí)行dexopt(用于優(yōu)化dex文件)的內(nèi)存只分配了5M
  2. 一個(gè)dex文件最多只支持65536個(gè)方法沸版。

解決方案:

  1. 使用Multidex嘁傀,將編譯好的class文件拆分打包成兩個(gè)dex,繞過dex方法數(shù)量的限制以及安裝時(shí)的檢查视粮,在運(yùn)行時(shí)再動(dòng)態(tài)加載第二個(gè)dex文件中心包。
  2. 使用插件化,將功能模塊分離馒铃,減少宿主apk的大小和代碼蟹腾。

這里主要來說說Multidex的原理。

基本原理:

  1. 除了第一個(gè)dex文件(即正常apk包唯一包含的Dex文件)区宇,其它dex文件都以資源的方式放在安裝包中娃殖。所以我們需要將其他dex文件并在Application的onCreate回調(diào)中注入到系統(tǒng)的ClassLoader。并且對(duì)于那些在注入之前已經(jīng)引用到的類(以及它們所在的jar),必須放入第一個(gè)Dex文件中议谷。

  2. PathClassLoader作為默認(rèn)的類加載器炉爆,在打開應(yīng)用程序的時(shí)候PathClassLoader就去加載指定的apk(解壓成dex,然后在優(yōu)化成odex),也就是第一個(gè)dex文件是PathClassLoader自動(dòng)加載的芬首。所以赴捞,我們需要做的就是將其他的dex文件注入到這個(gè)PathClassLoader中去。

  3. 因?yàn)镻athClassLoader和DexClassLoader的原理基本一致郁稍,從前面的分析來看赦政,我們知道PathClassLoader里面的dex文件是放在一個(gè)Element數(shù)組里面,可以包含多個(gè)dex文件耀怜,每個(gè)dex文件是一個(gè)Element恢着,所以我們只需要將其他的dex文件放到這個(gè)數(shù)組中去就可以了。

實(shí)現(xiàn):

  1. 通過反射獲取PathClassLoader中的DexPathList中的Element數(shù)組(已加載了第一個(gè)dex包财破,由系統(tǒng)加載)
  2. 通過反射獲取DexClassLoader中的DexPathList中的Element數(shù)組(將第二個(gè)dex包加載進(jìn)去)
  3. 將兩個(gè)Element數(shù)組合并之后掰派,再將其賦值給PathClassLoader的Element數(shù)組

谷歌提供的MultiDex支持庫就是按照這個(gè)思路來實(shí)現(xiàn)的。

如何使用:

1左痢、修改Gradle的配置靡羡,支持multidex:

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.0"
    defaultConfig {
        ...
        minSdkVersion 14
        targetSdkVersion 21
        ...
        // Enabling multidex support.
        multiDexEnabled true
    }
    ...
}
dependencies {
  compile 'com.android.support:multidex:1.0.0'
}

在manifest文件中,在application標(biāo)簽下添加MultidexApplication Class的引用俊性,如下所示:

<!--?xml version="1.0" encoding="utf-8"?-->
<manifest package="com.example.android.multidex.myapplication" xmlns:android="http://schemas.android.com/apk/res/android">
     
        ...
    </application>
</manifest>

了解一下源碼:

MultiDexApplication類亿眠。

public class MultiDexApplication extends Application {
    public MultiDexApplication() {
    }
 
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}

原來要求使用MultiDexApplication的原因就是它重寫了Application,主要是為了將其他dex文件注入到系統(tǒng)的ClassLoader磅废。

MultiDex.install(this)方法。

//源碼
public static void install(Context context) {
    if(IS_VM_MULTIDEX_CAPABLE) {
        Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
    // 可以看到荆烈,MultiDex不支持SDK版本小于4的系統(tǒng)
    } else if(VERSION.SDK_INT < 4) {
        throw new RuntimeException("Multi dex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
    } else {
        try {
            // 獲取到應(yīng)用信息
            ApplicationInfo e = getApplicationInfo(context);
            if(e == null) {
                return;
            }
 
            Set var2 = installedApk;
            synchronized(installedApk) {
                // 得到我們這個(gè)應(yīng)用的apk文件路徑
                // 拿到這個(gè)apk文件路徑之后拯勉,后面就可以從中提取出其他的dex文件
                // 并且加載dex放到一個(gè)Element數(shù)組中
                String apkPath = e.sourceDir;
                if(installedApk.contains(apkPath)) {
                    return;
                }
                // 將這個(gè)apk文件路徑放到一個(gè)set中
                installedApk.add(apkPath);
 
                // 得到classLoader,它就是PathClassLoader
                // 后面就可以從這個(gè)PathClassLoader中拿到DexPathList中的Element數(shù)組
                // 這個(gè)數(shù)組里面就包括由系統(tǒng)加載第一個(gè)dex包
                ClassLoader loader;
                try {
                    loader = context.getClassLoader();
                } catch (RuntimeException var9) {
                    Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", var9);
                    return;
                }
 
                // 得到apk解壓后得到的dex文件的存放目錄憔购,放到應(yīng)用的data目錄下
                File dexDir = new File(e.dataDir, SECONDARY_FOLDER_NAME);
 
                // 這個(gè)方法就是從apk中提取dex文件宫峦,放到data目錄下
                List files = MultiDexExtractor.load(context, e, dexDir, false);
                if(checkValidZipFiles(files)) {
                    // 這個(gè)方法就是將其他的dex文件注入到系統(tǒng)classloader中的具體操作
                    installSecondaryDexes(loader, dexDir, files);
                } else {
                    files = MultiDexExtractor.load(context, e, dexDir, true);
                    installSecondaryDexes(loader, dexDir, files);
                }
            }
        } catch (Exception var11) {
            Log.e("MultiDex", "Multidex installation failure", var11);
            throw new RuntimeException("Multi dex installation failed (" + var11.getMessage() + ").");
        }
    }
}

//重構(gòu)代碼
public static void install(Context context) {
    Log.i(TAG, "install");
    // 1. 判讀是否需要執(zhí)行MultiDex。
    if (IS_VM_MULTIDEX_CAPABLE) {
        Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
        return;
    }
    if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
        throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT
                + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
    }
    try {
        ApplicationInfo applicationInfo = getApplicationInfo(context);
        if (applicationInfo == null) {
            // Looks like running on a test Context, so just return without patching.
            return;
        }
        // 2. 如果這個(gè)方法已經(jīng)調(diào)用過一次玫鸟,就不能再調(diào)用了导绷。
        synchronized (installedApk) {
            String apkPath = applicationInfo.sourceDir;
            if (installedApk.contains(apkPath)) {
                return;
            }
            installedApk.add(apkPath);
            // 3. 如果當(dāng)前Android版本已經(jīng)自身支持了MultiDex,依然可以執(zhí)行MultiDex操作屎飘,
            // 但是會(huì)有警告妥曲。
            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") + "\"");
            }
            // 4. 獲取當(dāng)前的ClassLoader實(shí)例,后面要做的工作钦购,就是把其他dex文件加載后檐盟,
            // 把其DexFile對(duì)象添加到這個(gè)ClassLoader實(shí)例里就完事了。
            ClassLoader loader;
            try {
                loader = context.getClassLoader();
            } catch (RuntimeException e) {
                Log.w(TAG, "Failure while trying to obtain Context class loader. " +
                        "Must be running in test mode. Skip patching.", e);
                return;
            }
            if (loader == null) {
                Log.e(TAG,
                        "Context class loader is null. Must be running in test mode. "
                        + "Skip patching.");
                return;
            }
            try {
              // 5. 清除舊的dex文件押桃,注意這里不是清除上次加載的dex文件緩存葵萎。
              // 獲取dex緩存目錄是,會(huì)優(yōu)先獲取/data/data/<package>/code-cache作為緩存目錄。
              // 如果獲取失敗羡忘,則使用/data/data/<package>/files/code-cache目錄谎痢。
              // 這里清除的是第二個(gè)目錄。
              clearOldDexDir(context);
            } catch (Throwable t) {
              Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
                  + "continuing without cleaning.", t);
            }
            // 6. 獲取緩存目錄(/data/data/<package>/code-cache)卷雕。
            File dexDir = getDexDir(context, applicationInfo);
            // 7. 加載緩存文件(如果有)节猿。
            List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
            // 8. 檢查緩存的dex是否安全
            if (checkValidZipFiles(files)) {
                // 9. 安裝緩存的dex
                installSecondaryDexes(loader, dexDir, files);
            } else {
                // 9. 從apk壓縮包里面提取dex文件
                Log.w(TAG, "Files were not valid zip files.  Forcing a reload.");
                files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
                if (checkValidZipFiles(files)) {
                    // 10. 安裝提取的dex
                    installSecondaryDexes(loader, dexDir, files);
                } else {
                    throw new RuntimeException("Zip files were not valid.");
                }
            }
        }
    } catch (Exception e) {
        Log.e(TAG, "Multidex installation failure", e);
        throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");
    }
    Log.i(TAG, "install done");
}

具體代碼的分析已經(jīng)在上面代碼的注釋里給出了,從這里我們也可以看出爽蝴,整個(gè)MultiDex.install(Context)的過程中沐批,關(guān)鍵的步驟就是MultiDexExtractor#load方法和MultiDex#installSecondaryDexes方法。

(這部分是題外話)其中有個(gè)MultiDex#clearOldDexDir(Context)方法蝎亚,這個(gè)方法的作用是刪除/data/data//files/code-cache九孩,一開始我以為這個(gè)方法是刪除上一次執(zhí)行MultiDex后的緩存文件,不過這明顯不對(duì)发框,不可能每次MultiDex都重新解壓dex文件一邊躺彬,這樣每次啟動(dòng)會(huì)很耗時(shí),只有第一次冷啟動(dòng)的時(shí)候才需要解壓dex文件梅惯。后來我又想是不是以前舊版的MultiDex曾經(jīng)把緩存文件放在這個(gè)目錄里宪拥,現(xiàn)在新版本只是清除以前舊版的遺留文件?但是我找遍了整個(gè)MultiDex Repo的提交也沒有見過類似的舊版本代碼铣减。后面我仔細(xì)看MultiDex#getDexDir這個(gè)方法才發(fā)現(xiàn)她君,原來MultiDex在獲取dex緩存目錄是,會(huì)優(yōu)先獲取/data/data//code-cache作為緩存目錄葫哗,如果獲取失敗缔刹,則使用/data/data//files/code-cache目錄,而后者的緩存文件會(huì)在每次App重新啟動(dòng)的時(shí)候被清除劣针。感覺MultiDex獲取緩存目錄的邏輯不是很嚴(yán)謹(jǐn)校镐,而獲取緩存目錄失敗也是MultiDex工作工程中少數(shù)有重試機(jī)制的地方,看來MultiDex真的是一個(gè)臨時(shí)的兼容方案捺典,Google也許并不打算認(rèn)真處理這些歷史的黑鍋鸟廓。

MultiDexExtractor.load方法

static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,
            boolean forceReload) throws IOException {
        Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");
        final File sourceApk = new File(applicationInfo.sourceDir);
        // 1. 獲取當(dāng)前Apk文件的crc值。
        long currentCrc = getZipCrc(sourceApk);
        // Validity check and extraction must be done only while the lock file has been taken.
        File lockFile = new File(dexDir, LOCK_FILENAME);
        RandomAccessFile lockRaf = new RandomAccessFile(lockFile, "rw");
        FileChannel lockChannel = null;
        FileLock cacheLock = null;
        List<File> files;
        IOException releaseLockException = null;
        try {
            lockChannel = lockRaf.getChannel();
            Log.i(TAG, "Blocking on lock " + lockFile.getPath());
            // 2. 加上文件鎖襟己,防止多進(jìn)程沖突引谜。
            cacheLock = lockChannel.lock();
            Log.i(TAG, lockFile.getPath() + " locked");
            // 3. 先判斷是否強(qiáng)制重新解壓,這里第一次會(huì)優(yōu)先使用已解壓過的dex文件擎浴,如果加載失敗就強(qiáng)制重新解壓煌张。
            // 此外,通過crc和文件修改時(shí)間退客,判斷如果Apk文件已經(jīng)被修改(覆蓋安裝)骏融,就會(huì)跳過緩存重新解壓dex文件链嘀。
            if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
                try {
                    // 4. 加載緩存的dex文件
                    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);
                    // 5. 加載失敗的話重新解壓,并保存解壓出來的dex文件的信息档玻。
                    files = performExtractions(sourceApk, dexDir);
                    putStoredApkInfo(context,
                            getTimeStamp(sourceApk), currentCrc, files.size() + 1);
                }
            } else {
                // 4. 重新解壓怀泊,并保存解壓出來的dex文件的信息。
                Log.i(TAG, "Detected that extraction must be performed.");
                files = performExtractions(sourceApk, dexDir);
                putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
            }
        } finally {
            if (cacheLock != null) {
                try {
                    cacheLock.release();
                } catch (IOException e) {
                    Log.e(TAG, "Failed to release lock on " + lockFile.getPath());
                    // Exception while releasing the lock is bad, we want to report it, but not at
                    // the price of overriding any already pending exception.
                    releaseLockException = e;
                }
            }
            if (lockChannel != null) {
                closeQuietly(lockChannel);
            }
            closeQuietly(lockRaf);
        }
        if (releaseLockException != null) {
            throw releaseLockException;
        }
        Log.i(TAG, "load found " + files.size() + " secondary dex files");
        return files;
    }

這個(gè)過程主要是獲取可以安裝的dex文件列表误趴,可以是上次解壓出來的緩存文件霹琼,也可以是重新從Apk包里面提取出來的。需要注意的時(shí)凉当,如果是重新解壓枣申,這里會(huì)有明顯的耗時(shí),而且解壓出來的dex文件看杭,會(huì)被壓縮成.zip壓縮包忠藤,壓縮的過程也會(huì)有明顯的耗時(shí)(這里壓縮dex文件可能是問了節(jié)省空間)。

如果dex文件是重新解壓出來的楼雹,則會(huì)保存dex文件的信息模孩,包括解壓的apk文件的crc值、修改時(shí)間以及dex文件的數(shù)目贮缅,以便下一次啟動(dòng)直接使用已經(jīng)解壓過的dex緩存文件榨咐,而不是每一次都重新解壓。

需要特別提到的是谴供,里面的FileLock是最新的master分支里面新加進(jìn)去的功能块茁,現(xiàn)在最新的1.0.1版本里面是沒有的。

無論是通過使用緩存的dex文件桂肌,還是重新從apk中解壓dex文件数焊,獲取dex文件列表后,下一步就是安裝(或者說加載)這些dex文件了轴或。最后的工作在MultiDex#installSecondaryDexes這個(gè)方法里面。

MultiDex#installSecondaryDexes方法仰禀。

// loader對(duì)應(yīng)的就是PathClassLoader
// dexDir是dex的存放目錄
// files對(duì)應(yīng)的就是dex文件
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);
        }
    }
}

因?yàn)樵诓煌腟DK版本上照雁,ClassLoader(更準(zhǔn)確來說是DexClassLoader)加載dex文件的方式有所不同,所以這里做了V4/V14/V19的兼容(Magic Code)

Build.VERSION.SDK_INT < 14

/**
 * Installer for platform versions 4 to 13.
 */
private static final class V4 {
    private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
                    throws IllegalArgumentException, IllegalAccessException,
                    NoSuchFieldException, IOException {
        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);
        }
        // 這個(gè)版本是最簡(jiǎn)單的答恶。
        // 只需要?jiǎng)?chuàng)建DexFile對(duì)象后饺蚊,使用反射的方法分別擴(kuò)展ClassLoader實(shí)例的以下字段即可。
        pathField.set(loader, path.toString());
        expandFieldArray(loader, "mPaths", extraPaths);
        expandFieldArray(loader, "mFiles", extraFiles);
        expandFieldArray(loader, "mZips", extraZips);
        expandFieldArray(loader, "mDexs", extraDexs);
    }
}

MultiDex.V14.install方法悬嗓。

/**
 * Installer for platform versions 14, 15, 16, 17 and 18.
 */
private static final class V14 {
    private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
            File optimizedDirectory)
                    throws IllegalArgumentException, IllegalAccessException,
                    NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
        // 這個(gè)方法就是使用反射來得到loader的pathList字段
        Field pathListField = findField(loader, "pathList");
        // 得到loader的pathList字段后污呼,我們就可以得到這個(gè)字段的值,也就是DexPathList對(duì)象
        Object dexPathList = pathListField.get(loader);
        // 這個(gè)方法就是將其他的dex文件Element數(shù)組和第一個(gè)dex的Element數(shù)組合并
        //makeDexElements方法就是用來得到其他dex的Elements數(shù)組
        expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
    }
    private static Object[] makeDexElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory)
                    throws IllegalAccessException, InvocationTargetException,
                    NoSuchMethodException {
        Method makeDexElements =
                findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
        return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
    }
}

合并的過程expandFieldArray方法包竹。

// instance對(duì)應(yīng)的就是pathList對(duì)象
// fieldName 對(duì)應(yīng)的就是字段名燕酷,我們要得到的就是pathList對(duì)象里面的dexElements數(shù)組
// extraElements對(duì)應(yīng)的就是其他dex對(duì)應(yīng)的Element數(shù)組
private static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
    // 得到Element數(shù)組字段
    Field jlrField = findField(instance, fieldName);
    // 得到pathList對(duì)象里面的dexElements數(shù)組
    Object[] original = (Object[])((Object[])jlrField.get(instance));
    // 創(chuàng)建一個(gè)新的數(shù)組用來存放合并之后的結(jié)果
    Object[] combined = (Object[])((Object[])Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length));
    // 將第一個(gè)dex的Elements數(shù)組復(fù)制到創(chuàng)建的數(shù)組中去
    System.arraycopy(original, 0, combined, 0, original.length);
    // 將其他dex的Elements數(shù)組復(fù)制到創(chuàng)建的數(shù)組中去
    System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
    // 將得到的這個(gè)合并的新數(shù)組的值設(shè)置到pathList對(duì)象的Element數(shù)組字段上
    jlrField.set(instance, combined);
}

從API14開始籍凝,DexClassLoader會(huì)使用一個(gè)DexpDexPathList類來封裝DexFile數(shù)組。

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";
    private static Element[] makeDexElements(ArrayList<File> files,
            File optimizedDirectory) {
        ArrayList<Element> elements = new ArrayList<Element>();
        for (File file : files) {
            ZipFile zip = null;
            DexFile dex = null;
            String name = file.getName();
            if (name.endsWith(DEX_SUFFIX)) {
                // Raw dex file (not inside a zip/jar).
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                try {
                    zip = new ZipFile(file);
                } catch (IOException ex) {
                    System.logE("Unable to open zip file: " + file, ex);
                }
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ignored) {
                }
            } else {
                System.logW("Unknown file type for: " + file);
            }
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, zip, dex));
            }
        }
        return elements.toArray(new Element[elements.size()]);
    }
    private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }
}

通過調(diào)用DexPathList#makeDexElements方法苗缩,可以加載我們上面解壓得到的dex文件饵蒂,從代碼也可以看出,DexPathList#makeDexElements其實(shí)也是通過調(diào)用DexFile#loadDex來加載dex文件并創(chuàng)建DexFile對(duì)象的酱讶。V14中退盯,通過反射調(diào)用DexPathList#makeDexElements方法加載我們需要的dex文件,在把加載得到的數(shù)組擴(kuò)展到ClassLoader實(shí)例的”pathList”字段泻肯,從而完成dex文件的安裝渊迁。

從DexPathList的代碼中我們也可以看出,ClassLoader是支持直接加載.dex/.zip/.jar/.apk的dex文件包的(我記得以前在哪篇日志中好像提到過類似的問題…)灶挟。

19 <= Build.VERSION.SDK_INT

/**
 * Installer for platform versions 19.
 */
private static final class V19 {
    private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
            File optimizedDirectory)
                    throws IllegalArgumentException, IllegalAccessException,
                    NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
        Field pathListField = findField(loader, "pathList");
        Object dexPathList = pathListField.get(loader);
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
                suppressedExceptions));
        if (suppressedExceptions.size() > 0) {
            for (IOException e : suppressedExceptions) {
                Log.w(TAG, "Exception in makeDexElement", e);
            }
            Field suppressedExceptionsField =
                    findField(dexPathList, "dexElementsSuppressedExceptions");
            IOException[] dexElementsSuppressedExceptions =
                    (IOException[]) suppressedExceptionsField.get(dexPathList);
            if (dexElementsSuppressedExceptions == null) {
                dexElementsSuppressedExceptions =
                        suppressedExceptions.toArray(
                                new IOException[suppressedExceptions.size()]);
            } else {
                IOException[] combined =
                        new IOException[suppressedExceptions.size() +
                                        dexElementsSuppressedExceptions.length];
                suppressedExceptions.toArray(combined);
                System.arraycopy(dexElementsSuppressedExceptions, 0, combined,
                        suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
                dexElementsSuppressedExceptions = combined;
            }
            suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
        }
    }
    private static Object[] makeDexElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory,
            ArrayList<IOException> suppressedExceptions)
                    throws IllegalAccessException, InvocationTargetException,
                    NoSuchMethodException {
        Method makeDexElements =
                findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
                        ArrayList.class);
        return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
                suppressedExceptions);
    }
}

V19與V14差別不大琉朽,只不過DexPathList#makeDexElements方法多了一個(gè)ArrayList參數(shù),如果在執(zhí)行DexPathList#makeDexElements方法的過程中出現(xiàn)異常膏萧,后面使用反射的方式把這些異常記錄進(jìn)DexPathList的dexElementsSuppressedExceptions字段里面漓骚。

無論是V4/V14還是V19,在創(chuàng)建DexFile對(duì)象的時(shí)候榛泛,都需要通過DexFile的Native方法openDexFile來打開dex文件蝌蹂,其具體細(xì)節(jié)暫不討論(涉及到dex的文件結(jié)構(gòu),很煩曹锨,有興趣請(qǐng)閱讀dalvik_system_DexFile.cpp)孤个,這個(gè)過程的主要目的是給當(dāng)前的dex文件做Optimize優(yōu)化處理并生成相同文件名的odex文件,App實(shí)際加載類的時(shí)候沛简,都是通過odex文件進(jìn)行的齐鲤。因?yàn)槊總€(gè)設(shè)備對(duì)odex格式的要求都不一樣,所以這個(gè)優(yōu)化的操作只能放在安裝Apk的時(shí)候處理椒楣,主dex的優(yōu)化我們已經(jīng)在安裝apk的時(shí)候搞定了给郊,其余的dex就是在MultiDex#installSecondaryDexes里面優(yōu)化的,而后者也是MultiDex過程中捧灰,另外一個(gè)耗時(shí)比較多的操作淆九。(在MultiDex中,提取出來的dex文件被壓縮成.zip文件毛俏,又優(yōu)化后的odex文件則被保存為.dex文件炭庙。)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市煌寇,隨后出現(xiàn)的幾起案子焕蹄,更是在濱河造成了極大的恐慌,老刑警劉巖阀溶,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腻脏,死亡現(xiàn)場(chǎng)離奇詭異鸦泳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)迹卢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門辽故,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腐碱,你說我怎么就攤上這事誊垢。” “怎么了症见?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵喂走,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我谋作,道長(zhǎng)芋肠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任遵蚜,我火速辦了婚禮帖池,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吭净。我一直安慰自己睡汹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布寂殉。 她就那樣靜靜地躺著囚巴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪友扰。 梳的紋絲不亂的頭發(fā)上彤叉,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音村怪,去河邊找鬼秽浇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甚负,可吹牛的內(nèi)容都是我干的柬焕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼腊敲,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼击喂!你這毒婦竟也來了维苔?” 一聲冷哼從身側(cè)響起碰辅,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎介时,沒想到半個(gè)月后没宾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凌彬,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年循衰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铲敛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡会钝,死狀恐怖伐蒋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迁酸,我是刑警寧澤先鱼,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站奸鬓,受9級(jí)特大地震影響焙畔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜串远,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一宏多、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧澡罚,春花似錦伸但、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至催式,卻和暖如春函喉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荣月。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工管呵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哺窄。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓碟刺,卻偏偏與公主長(zhǎng)得像板驳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容