美文网首页
MultiDex源码分析

MultiDex源码分析

作者: Altima | 来源:发表于2016-07-08 18:56 被阅读722次
    一、Android虚拟机加载class原理

    我们知道Java在运行时加载对应的类是通过ClassLoader来实现的,ClassLoader本身是一个抽象来,Android中使用PathClassLoader类作为Android的默认的类加载器,
    PathClassLoader其实实现的就是简单的从文件系统中加载类文件。PathClassLoade本身继承自BaseDexClasoader,BaseDexClassLoader重写了findClass方法。

    详细的可以看这篇文章:Android热更新实现原理,本篇文章不做介绍,这里只从源码上讲解multidex的实现原理。

    二、源码解析
    1. 首先从Multidex.install方法开始分析,以下是install的核心代码:
        public static void install(Context context) {
            Log.i(TAG, "install");
            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;
                }
    
                synchronized (installedApk) {
                    String apkPath = applicationInfo.sourceDir;
                    if (installedApk.contains(apkPath)) {
                        return;
                    }
                    installedApk.add(apkPath);
    
                    if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
                            ......
                    }
    
                    ClassLoader loader;
                    try {
                        //这里获取到BaseDexClassLoader的类加载器。
                        loader = context.getClassLoader();
                    } catch (RuntimeException e) {
                        return;
                    }
                    if (loader == null) {
                        return;
                    }
    
                    try {
                      clearOldDexDir(context);
                    } catch (Throwable t) {
                    
                    }
    
                    File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
                    List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
                    if (checkValidZipFiles(files)) {
                        //这里加载第二个dex文件
                        installSecondaryDexes(loader, dexDir, files);
                    } else {
                        // Try again, but this time force a reload of the zip file.
                        files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
                        if (checkValidZipFiles(files)) {
                            installSecondaryDexes(loader, dexDir, files);
                        } else {
                            // Second time didn't work, give up
                            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");
        }
    

    我们先分析下面这段代码:

        private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version"));
        private static final int MIN_SDK_VERSION = 4; //4是android 1.6
        
        public static void install(Context context) {
            Log.i(TAG, "install");
            //isVMMultidexCapable()方法,判断虚拟机是否支持multidex
            if (IS_VM_MULTIDEX_CAPABLE) {
                Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
                return;
            }
            //在sdk1.6以前是不支持multidex的
            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 + ".");
            }
            .......
        }
    
    

    我们看下isVMMultidexCapable方法:

        //这里的versionString是 java.vm.version
        static boolean isVMMultidexCapable(String versionString) {
            boolean isMultidexCapable = false;
            if (versionString != null) {
                Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
                if (matcher.matches()) {
                    try {
                        int major = Integer.parseInt(matcher.group(1));
                        int minor = Integer.parseInt(matcher.group(2));
                        isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
                                || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
                                        && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
                    } catch (NumberFormatException e) {
                        // let isMultidexCapable be false
                    }
                }
            }
            Log.i(TAG, "VM with version " + versionString +
                    (isMultidexCapable ?
                            " has multidex support" :
                            " does not have multidex support"));
            return isMultidexCapable;
        }
    

    getProperty()的参数:

    mean name example
    java.vm.version VM implementation version 1.2.0

    这里主要判断虚拟机是否已经支持multidex,如不支持就直接返回。

    继续分析,经过一系列的判断,验证等步骤,最终会执行installSecondaryDexes方法,这个方法才是加载我们拆分的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);
                }
            }
        }
    

    首先判断,当前的sdk版本,有3个不同的方法,分别是V19 V14 V4,我们分析下V19.install方法

            private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                    File optimizedDirectory)
                            throws IllegalArgumentException, IllegalAccessException,
                            NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
                /* The patched class loader is expected to be a descendant of
                 * dalvik.system.BaseDexClassLoader. We modify its
                 * dalvik.system.DexPathList pathList field to append additional DEX
                 * file entries.
                 */
                 //这里通过反射获取BaseClassLoader中的变量名为”pathList“ Field
                Field pathListField = findField(loader, "pathList");
                //这里拿到pathList这个对象
                Object dexPathList = pathListField.get(loader);
                ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
                //关键步骤,反射调用makeDexElements函数,
                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(loader, "dexElementsSuppressedExceptions");
                    IOException[] dexElementsSuppressedExceptions =
                            (IOException[]) suppressedExceptionsField.get(loader);
    
                    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(loader, dexElementsSuppressedExceptions);
                }
            }
    
    

    这里的pathDexList是BaseClassLoader下面的一个成员变量,它的类型是DexPathList,这个类主要负责加载Dex并重组;接着看下expandFieldArray的源码,这个函数主要的目的是,先通过反射拿到名为dexElements的field,这个dexElements就是我们刚刚说的Elements数组,然后将刚刚makeDexElements生成的Elements数组放在dexElements的首部,makeDexElements生成的数组就是加载Dex文件的数组,这样就完成了将dex的class放在ClassLoader前面的功能。

        /**
         * Replace the value of a field containing a non null array, by a new array containing the
         * elements of the original array plus the elements of extraElements.
         * @param instance the instance whose field is to be modified.
         * @param fieldName the field to modify.
         * @param extraElements elements to append at the end of the array.
         */
        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);
        }
    

    最后分析下makeDexElements这个函数,首先调用的是V19下面的makeDexElements函数:

            /**
             * A wrapper around
             * {@code private static final dalvik.system.DexPathList#makeDexElements}.
             */
            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);
            }
    

    事实上,也是通过反射调用DexPathList里面的makeDexElements方法,具体就不做分析。

    
        private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
            ArrayList<Element> elements = new ArrayList<Element>();
    
            for (File file : files) {
                File zip = null;
                DexFile dex = null;
                String name = file.getName();
    
                if (file.isDirectory()) {
                    elements.add(new Element(file, true, null, null));
                } else if (file.isFile()){
                    if (name.endsWith(DEX_SUFFIX)) {
                        try {
                            dex = loadDexFile(file, optimizedDirectory);
                        } catch (IOException ex) {
                            System.logE("Unable to load dex file: " + file, ex);
                        }
                    } else {
                        zip = file;
    
                        try {
                            dex = loadDexFile(file, optimizedDirectory);
                        } catch (IOException suppressed) {
                            suppressedExceptions.add(suppressed);
                        }
                    }
                } else {
                    System.logW("ClassLoader referenced unknown path: " + file);
                }
    
                if ((zip != null) || (dex != null)) {
                    elements.add(new Element(file, false, zip, dex));
                }
            }
    
            return elements.toArray(new Element[elements.size()]);
        }
    

    相关文章

      网友评论

          本文标题:MultiDex源码分析

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