MultiDex分包原理解析

隨著app的迭代祭埂,app的功能越來越多,導(dǎo)致app的大小也相應(yīng)增大兵钮,很多app已經(jīng)超過了100M蛆橡,成了超級app。但安卓的方法數(shù)有一個(gè)限制掘譬,不能超過65536泰演,針對這個(gè)問題,google給出了解決方案葱轩,超過后睦焕,可以進(jìn)行分包解決藐握。

使用也很簡單,直接調(diào)用MultiDex.install(this)就可以垃喊。

基本原理是jvm在方法區(qū)加載class文件猾普,下次使用時(shí),如果加載過了本谜,就可以直接用來使用初家。而在查找類時(shí),會從classloader里面pathList里面查找乌助,遍歷pathList里面的dexElements集合溜在,找到后就直接返回。
所以我們只需要把分dex的dexElements加到主dex的dexElements里面就可以了他托。

好的掖肋,我們來看一下具體怎么實(shí)現(xiàn)

public class MultiDexApplication extends Application {
    public MultiDexApplication() {
    }

    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}

我們跟著源碼來一步步揭開它的面紗。

public final class MultiDex {

    static {
        //分dex的目錄赏参。打包的時(shí)候志笼,如果方法數(shù)超了,會生成class.dex,class1.dex,class2.dex文件
        SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
        installedApk = new HashSet();
        //系統(tǒng)是否支持分包
        IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version"));
    }

    public static void install(Context context) {
        Log.i("MultiDex", "install");
        if (IS_VM_MULTIDEX_CAPABLE) {
            Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
        } 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 {
                ApplicationInfo applicationInfo = getApplicationInfo(context);
                if (applicationInfo == null) {
                    return;
                }

                Set var2 = installedApk;
                synchronized(installedApk) {
                    String apkPath = applicationInfo.sourceDir;
                    //已經(jīng)處理過來把篓,不需要再處理
                    if (installedApk.contains(apkPath)) {
                        return;
                    }

                    installedApk.add(apkPath);
                    ClassLoader loader;
                    try {
                        loader = context.getClassLoader();
                    } catch (RuntimeException var9) {
                        return;
                    }

                    try {
                        //清空dex文件
                        clearOldDexDir(context);
                    } catch (Throwable var8) {
                    }

                    File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
                        //將分包的dex文件提取出來纫溃,files是文件列表
                    List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
                    if (checkValidZipFiles(files)) {
                        //安裝分的dex
                        installSecondaryDexes(loader, dexDir, files);
                    } else {
                        files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
                        if (!checkValidZipFiles(files)) {
                            throw new RuntimeException("Zip files were not valid.");
                        }

                        installSecondaryDexes(loader, dexDir, files);
                    }
                }
            } catch (Exception var11) {
                Log.e("MultiDex", "Multidex installation failure", var11);
                throw new RuntimeException("Multi dex installation failed (" + var11.getMessage() + ").");
            }

            Log.i("MultiDex", "install done");
        }
    }


    private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
        if (!files.isEmpty()) {
            //根據(jù)系統(tǒng)版本來安裝,我們看一下大于19的
            if (VERSION.SDK_INT >= 19) {
                MultiDex.V19.install(loader, files, dexDir);
            } else if (VERSION.SDK_INT >= 14) {
                MultiDex.V14.install(loader, files, dexDir);
            } else {
                MultiDex.V4.install(loader, files);
            }
        }

    }
}

final class MultiDexExtractor {
    static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload) throws IOException {
        Log.i("MultiDex", "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");
        File sourceApk = new File(applicationInfo.sourceDir);
        long currentCrc = getZipCrc(sourceApk);
        List files;
        //首次進(jìn)入else
        if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
            try {
                files = loadExistingExtractions(context, sourceApk, dexDir);
            } catch (IOException var9) {
                Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var9);
                files = performExtractions(sourceApk, dexDir);
                putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
            }
        } else {
             //提取dex文件
            files = performExtractions(sourceApk, dexDir);
            //將提取的時(shí)間和分包的dex數(shù)量記錄到sp
            putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
        }
        return files;
    }

    private static List<File> performExtractions(File sourceApk, File dexDir) throws IOException {
        String extractedFilePrefix = sourceApk.getName() + ".classes";
        //刪除除去extractedFilePrefix的其它文件
        prepareDexDir(dexDir, extractedFilePrefix);
        List<File> files = new ArrayList();
        ZipFile apk = new ZipFile(sourceApk);

        try {
             //分的dex的后綴是從2開始的
            int secondaryNumber = 2;
            for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
                String fileName = extractedFilePrefix + secondaryNumber + ".zip";
                File extractedFile = new File(dexDir, fileName);
                files.add(extractedFile);
                int numAttempts = 0;
                boolean isExtractionSuccessful = false;
                //提取dex文件可能失敗纸俭,嘗試3次
                while(numAttempts < 3 && !isExtractionSuccessful) {
                    ++numAttempts;
                    //將dexFile提取成extractedFile
                    extract(apk, dexFile, extractedFile, extractedFilePrefix);
                    isExtractionSuccessful = verifyZipFile(extractedFile);
                    Log.i("MultiDex", "Extraction " + (isExtractionSuccessful ? "success" : "failed") + " - length " + extractedFile.getAbsolutePath() + ": " + extractedFile.length());
                    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;
            }
        } finally {
            try {
                apk.close();
            } catch (IOException var16) {
                Log.w("MultiDex", "Failed to close resource", var16);
            }

        }

        return files;
    }
}

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

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
            //找到classloader里面的pathList變量皇耗,該變量是dex元素的集合
            Field pathListField = MultiDex.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList();
             //makeDexElements,根據(jù)dex文件列表揍很,生成對應(yīng)的dexElements郎楼,然后通過expandFieldArray方法,拼接到主dex的dexElements后面窒悔,這樣就將分包的dex跟主dex列表合并成一個(gè)來
            MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
            if (suppressedExceptions.size() > 0) {
                Iterator i$ = suppressedExceptions.iterator();

                while(i$.hasNext()) {
                    IOException e = (IOException)i$.next();
                    Log.w("MultiDex", "Exception in makeDexElement", e);
                }

                Field suppressedExceptionsField = MultiDex.findField(loader, "dexElementsSuppressedExceptions");
                IOException[] dexElementsSuppressedExceptions = (IOException[])((IOException[])suppressedExceptionsField.get(loader));
                if (dexElementsSuppressedExceptions == null) {
                    dexElementsSuppressedExceptions = (IOException[])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(loader, dexElementsSuppressedExceptions);
            }

        }

        private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
            return (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呜袁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子简珠,更是在濱河造成了極大的恐慌阶界,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聋庵,死亡現(xiàn)場離奇詭異膘融,居然都是意外死亡诈悍,警方通過查閱死者的電腦和手機(jī)除呵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門胳嘲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜂桶,“玉大人,你說我怎么就攤上這事指巡≡槿希” “怎么了队魏?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長臼疫。 經(jīng)常有香客問我择份,道長,這世上最難降的妖魔是什么烫堤? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任荣赶,我火速辦了婚禮,結(jié)果婚禮上塔逃,老公的妹妹穿的比我還像新娘讯壶。我一直安慰自己料仗,他們只是感情好湾盗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著立轧,像睡著了一般格粪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氛改,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天帐萎,我揣著相機(jī)與錄音,去河邊找鬼胜卤。 笑死疆导,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葛躏。 我是一名探鬼主播澈段,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舰攒!你這毒婦竟也來了败富?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤摩窃,失蹤者是張志新(化名)和其女友劉穎兽叮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猾愿,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹦聪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒂秘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泽本。...
    茶點(diǎn)故事閱讀 38,664評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖材彪,靈堂內(nèi)的尸體忽然破棺而出观挎,到底是詐尸還是另有隱情琴儿,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布嘁捷,位于F島的核電站造成,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雄嚣。R本人自食惡果不足惜晒屎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缓升。 院中可真熱鬧鼓鲁,春花似錦、人聲如沸港谊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽歧寺。三九已至燥狰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斜筐,已是汗流浹背龙致。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顷链,地道東北人目代。 一個(gè)月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像嗤练,于是被迫代替她去往敵國和親榛了。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評論 2 349

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