MultiDex源码分析
一、Android虚拟机加载class原理
我们知道Java在运行时加载对应的类是通过ClassLoader来实现的,ClassLoader本身是一个抽象来,Android中使用PathClassLoader类作为Android的默认的类加载器,
PathClassLoader其实实现的就是简单的从文件系统中加载类文件。PathClassLoade本身继承自BaseDexClasoader,BaseDexClassLoader重写了findClass方法。
详细的可以看这篇文章:Android热更新实现原理,本篇文章不做介绍,这里只从源码上讲解multidex的实现原理。
二、源码解析
- 首先从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 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 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 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 suppressedExceptions = new ArrayList(); //关键步骤,反射调用makeDexElements函数, expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(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 files, File optimizedDirectory, ArrayList 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 files, File optimizedDirectory, ArrayList suppressedExceptions) { ArrayList elements = new ArrayList(); 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()]); }
更多相关文章
- Android(安卓)多媒体应用——MediaRecorder录制音频
- 2018-08-07
- AndroidStudioTip--用Builder模式替代构造方法
- android 图形系统requestLayout的流程
- 【Android(安卓)Developers Training】 104. 接受地点更新
- Android解决加载大图片时内存溢出的问题
- popupwindow的一些注意事项
- android方法数超过64k和Gradle编译OOM解决方法
- Android(安卓)Vendor Test Suite (VTS) 的概念、作用及测试方法