MultiDex分析

作者: act262 | 来源:发表于2018-01-09 20:46 被阅读90次

    MultiDex support 包是为了解决SDK 20以前单个dex文件的方法数量限制问题(65535 = 64k方法数问题)

    MultiDex#install() -> doInstallation()

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

    1. 从安装位置的apk包(/data/app/pkg-1.apk)中提取classesN.dex文件到数据区(/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文件保存了其对应的信息,如果文件被修改,那么会触发重新提取的操作

    // 2. 直接执行已提取过的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());
        }
        
        // 反射获取指定对象的方法对象,用于后面调用改方法
        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());
        }
        
        // 将指定对象的字段值加上新的内容值
        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内容              
                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);
                }
            }
            
            // 调用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

    相关文章

      网友评论

        本文标题:MultiDex分析

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