MultiDex分析

MultiDex support 包是為了解決SDK 20以前單個dex文件的方法數(shù)量限制問題(65535 = 64k方法數(shù)問題)

MultiDex#install() -> doInstallation()

dex 保存位置 /data/data/pkg/code_cache/secondary-dexes

1. 從安裝位置的apk包(/data/app/pkg-1.apk)中提取classesN.dex文件到數(shù)據(jù)區(qū)(/data/data/pkg/code_cache/secondary-dexes/xxx.zip)

private static void doInstallation(Context mainContext, File sourceApk, File dataDir,
            String secondaryFolderName, String prefsKeyPrefix) throws IOException,
                IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
                InvocationTargetException, NoSuchMethodException {
            // ...
            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/code_cache/secondary-dexes/
            File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
            
            // 加載dex文件
            List<? extends File> files =
                    MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false);
            // 注入second dex        
            installSecondaryDexes(loader, dexDir, files);
        }
    }

// 1. 首次、重新提取dex 文件
MultiDexExtractor#load() -> performExtractions()

將apk包中classes2.dex、classes3.dex...等提取出來,放到/data/data/pkg/code_cache/secondary-dexes/目錄下,然后返回這些List<File>

提取出來的dex文件保存了其對應的信息眼耀,如果文件被修改,那么會觸發(fā)重新提取的操作

// 2. 直接執(zhí)行已提取過的dex zip文件
MultiDexExtractor#load() -> loadExistingExtractions()

2. 注入second dex

    private static void installSecondaryDexes(ClassLoader loader, File dexDir,
        List<? extends 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);
            }
        }
    }
// 反射獲取指定對象的字段對象净蚤,用于后面修改對象值
private static Field findField(Object instance, String name) throws NoSuchFieldException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(name);
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                return field;
            } catch (NoSuchFieldException e) {
                // ignore and search next
            }
        }
        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    }
    
    // 反射獲取指定對象的方法對象骨坑,用于后面調(diào)用改方法
    private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
            throws NoSuchMethodException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Method method = clazz.getDeclaredMethod(name, parameterTypes);
                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }
                return method;
            } catch (NoSuchMethodException e) {
                // ignore and search next
            }
        }
        throw new NoSuchMethodException("Method " + name + " with parameters " +
                Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
    }
    
    // 將指定對象的字段值加上新的內(nèi)容值
    private static void expandFieldArray(Object instance, String fieldName,
            Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
            IllegalAccessException {
        Field jlrField = findField(instance, fieldName);
        Object[] original = (Object[]) jlrField.get(instance);
        Object[] combined = (Object[]) Array.newInstance(
                original.getClass().getComponentType(), original.length + extraElements.length);
        System.arraycopy(original, 0, combined, 0, original.length);
        System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
        jlrField.set(instance, combined);
    }

v19

    /**
     * Installer for platform versions 19.
     */
    private static final class V19 {
        private static void install(ClassLoader loader,
                List<? extends 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>();
            // 在DexPathList#dexElements[] 附加上SecondDex內(nèi)容              
            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);
            }
        }
        
        // 調(diào)用DexPathList#makeDexElements()生成 `Element[]`
        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);
        }
    }

這里的操作是把額外的dex文件附加到classloader中去

相似的做法是用來做熱修復的方式,把修復的dex插入到前面去

MultiDex 源碼

Dalvik源碼

http://androidxref.com/8.0.0_r4/search?q=BaseDexClassLoader&project=libcore

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狭瞎,一起剝皮案震驚了整個濱河市细移,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熊锭,老刑警劉巖弧轧,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碗殷,居然都是意外死亡精绎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門锌妻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來代乃,“玉大人,你說我怎么就攤上這事「橄牛” “怎么了原茅?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堕仔。 經(jīng)常有香客問我擂橘,道長,這世上最難降的妖魔是什么摩骨? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任通贞,我火速辦了婚禮,結(jié)果婚禮上恼五,老公的妹妹穿的比我還像新娘滑频。我一直安慰自己,他們只是感情好唤冈,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布峡迷。 她就那樣靜靜地躺著,像睡著了一般你虹。 火紅的嫁衣襯著肌膚如雪绘搞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天傅物,我揣著相機與錄音夯辖,去河邊找鬼。 笑死董饰,一個胖子當著我的面吹牛蒿褂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播卒暂,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼啄栓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了也祠?” 一聲冷哼從身側(cè)響起昙楚,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诈嘿,沒想到半個月后堪旧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡奖亚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年淳梦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昔字。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡爆袍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情螃宙,我是刑警寧澤蛮瞄,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站谆扎,受9級特大地震影響挂捅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堂湖,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一闲先、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧无蜂,春花似錦伺糠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至酣倾,卻和暖如春舵揭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躁锡。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工午绳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人映之。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓拦焚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杠输。 傳聞我的和親對象是個殘疾皇子赎败,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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