相关文章链接:
---------------------------------------------------------------------------------
Android源码版本:5.0.2_r1
下面是多dex加载的时序图:
Android项目有两种方式支持多dex:
1. 项目中的Application类继承MultiDexApplication。
2. 在自己的Application类的attachBaseContext方法中调用MultiDex.install(this);。
我从MultiDexApplication这个类开始分析。
MultiDexApplication类继承了Application,并重载了attachBaseContext方法,在这个方法中调用了MultiDex.install(this);。
public class MultiDexApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
/** * Patches the application context class loader by appending extra dex files * loaded from the application apk. This method should be called in the * attachBaseContext of your {@link Application}, see * {@link MultiDexApplication} for more explanation and an example. * * @param context application context. * @throws RuntimeException if an error occurred preventing the classloader * extension. */ public static void install(Context context) { Log.i(TAG, "install"); ...... 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; // installedApk的类型是:Set<String>。 // 如果这个apk已经安装,则不重复安装。 if (installedApk.contains(apkPath)) { return; } installedApk.add(apkPath); ...... // 类加载器应该直接或间接继承于BaseDexClassLoader。 // 修改BaseDexClassLoader类中的DexPathList pathList字段,追加额外的DEX文件项。 /* 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. */ ClassLoader loader; ...... // dex将会输出到SECONDARY_FOLDER_NAME目录。 File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME); List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); // 校验这些zip文件是否合法。 if (checkValidZipFiles(files)) { // 安装提取出来的zip文件。 installSecondaryDexes(loader, dexDir, files); } else { Log.w(TAG, "Files were not valid zip files. Forcing a reload."); // 最后一个参数是true,代表强制加载。 // Try again, but this time force a reload of the zip file. files = MultiDexExtractor.load(context, applicationInfo, dexDir, true); // 校验这些zip文件是否合法。 if (checkValidZipFiles(files)) { // 安装提取出来的zip文件。 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"); }
/** * 提取/获得apk中多dex的提取zip文件。 * 如果不是加载已经存在的文件的情况,则还要保存apk的信息:时间戳、crc值、apk中dex的总个数。 * * Extracts application secondary dexes into files in the application data * directory. * * @return a list of files that were created. The list may be empty if there * are no secondary dex files. * @throws IOException if encounters a problem while reading or writing * secondary dex files */ static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload) throws IOException { Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")"); final File sourceApk = new File(applicationInfo.sourceDir); long currentCrc = getZipCrc(sourceApk); List<File> files; // isModified方法判断apk是否被修改过。 if (!forceReload && !isModified(context, sourceApk, currentCrc)) { try { // 加载已经存在的文件,如果有的文件不存在,或者不是zip文件,则会抛出异常。 files = loadExistingExtractions(context, sourceApk, dexDir); } catch (IOException ioe) { Log.w(TAG, "Failed to reload existing extracted secondary dex files," + " falling back to fresh extraction", ioe); // 从apk中提取出多dex,然后将这些dex逐个打包为zip文件,最终返回提取出来的zip文件列表。 files = performExtractions(sourceApk, dexDir); // getTimeStamp方法中调用的是sourceApk.lastModified()方法。 // putStoredApkInfo方法存储apk的信息:时间戳、crc值、apk中dex的总个数。 putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); } } else { Log.i(TAG, "Detected that extraction must be performed."); // 这里的performExtractions和putStoredApkInfo同上。 files = performExtractions(sourceApk, dexDir); putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); } Log.i(TAG, "load found " + files.size() + " secondary dex files"); return files; }
/** * 从apk中提取出多dex,然后将这些dex逐个打包为zip文件。 * @param sourceApk apk文件。 * @param dexDir 输出目录。 * @return 提取出来的zip文件列表。 */ private static List<File> performExtractions(File sourceApk, File dexDir) throws IOException { final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; // 如果文件没有正确的前缀,则删除。 // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that // contains a secondary dex file in there is not consistent with the latest apk. Otherwise, // multi-process race conditions can cause a crash loop where one process deletes the zip // while another had created it. prepareDexDir(dexDir, extractedFilePrefix); List<File> files = new ArrayList<File>(); final ZipFile apk = new ZipFile(sourceApk); try { int secondaryNumber = 2; ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); while (dexFile != null) { // 输出的文件名。 String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; // 输出的文件。 File extractedFile = new File(dexDir, fileName); files.add(extractedFile); Log.i(TAG, "Extraction is needed for file " + extractedFile); int numAttempts = 0; boolean isExtractionSuccessful = false; while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { numAttempts++; // 提取apk中的多dex文件,然后打包成一个zip文件。 // Create a zip file (extractedFile) containing only the secondary dex file // (dexFile) from the apk. extract(apk, dexFile, extractedFile, extractedFilePrefix); // 验证提取的文件是否是一个zip文件。 // Verify that the extracted file is indeed a zip file. isExtractionSuccessful = verifyZipFile(extractedFile); // Log the sha1 of the extracted zip file Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") + " - length " + extractedFile.getAbsolutePath() + ": " + extractedFile.length()); if (!isExtractionSuccessful) { // Delete the extracted file extractedFile.delete(); if (extractedFile.exists()) { Log.w(TAG, "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++; dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); } } finally { try { apk.close(); } catch (IOException e) { Log.w(TAG, "Failed to close resource", e); } } return files; }
private static void putStoredApkInfo(Context context, long timeStamp, long crc, int totalDexNumber) { SharedPreferences prefs = getMultiDexPreferences(context); SharedPreferences.Editor edit = prefs.edit(); edit.putLong(KEY_TIME_STAMP, timeStamp); // 时间戳 edit.putLong(KEY_CRC, crc); // crc值。 /* SharedPreferences.Editor doc says that apply() and commit() "atomically performs the * requested modifications" it should be OK to rely on saving the dex files number (getting * old number value would go along with old crc and time stamp). */ edit.putInt(KEY_DEX_NUMBER, totalDexNumber); // dex总个数。 apply(edit); }
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); } } }
/** * 安装多dex。 * @param loader * @param additionalClassPathEntries zip文件列表,这些zip文件中都只有一个文件classes.dex。 * @param optimizedDirectory 优化的dex存放的目录。 */ private static void install(ClassLoader loader, List<File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException { // 被打补丁的类加载器应该直接或间接继承BaseDexClassLoader。 // 我们修改DexPathList pathList字段,追加额外的DEX文件项。 /* 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. */ // dexPathList = load.pathList; Field pathListField = findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // makeDexElements方法调用了DexPathList中的makeDexElements方法,这个方法可以加载并优化dex、zip、jar。 // expandFieldArray方法将makeDexElements返回的数组patch到dexPathList.dexElements中。 expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList<File>(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); ...... }
/** * 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 (DexPathList.Element[]) dexPathList.makeDexElements(files, optimizedDirectory, suppressedExceptions) return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); }
/** * 原字段内容加上扩展的数组元素替换相应字段的内容,这个字段是一个数组。 * * 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 { // combined = new <Type>[original.length + extraElements.length]; 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); }
原文:http://blog.csdn.net/zylc369/article/details/44660007