加载多个dex

来源:互联网 发布:同花顺中天软件下载 编辑:程序博客网 时间:2024/06/05 01:05

下面是多dex加载的时序图: 



Android项目有两种方式支持多dex:

1. 项目中的Application类继承MultiDexApplication。
2. 在自己的Application类的attachBaseContext方法中调用MultiDex.install(this);


我从MultiDexApplication这个类开始分析。


MultiDexApplication类继承了Application,并重载了attachBaseContext方法,在这个方法中调用了MultiDex.install(this);

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MultiDexApplication extends Application {  
  2.   @Override  
  3.   protected void attachBaseContext(Context base) {  
  4.     super.attachBaseContext(base);  
  5.     MultiDex.install(this);  
  6.   }  
  7. }  

MultiDex.install方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Patches the application context class loader by appending extra dex files 
  3.  * loaded from the application apk. This method should be called in the 
  4.  * attachBaseContext of your {@link Application}, see 
  5.  * {@link MultiDexApplication} for more explanation and an example. 
  6.  * 
  7.  * @param context application context. 
  8.  * @throws RuntimeException if an error occurred preventing the classloader 
  9.  *         extension. 
  10.  */  
  11. public static void install(Context context) {  
  12.     Log.i(TAG, "install");  
  13.   
  14.     ......  
  15.   
  16.     try {  
  17.         ApplicationInfo applicationInfo = getApplicationInfo(context);  
  18.         if (applicationInfo == null) {  
  19.             // Looks like running on a test Context, so just return without patching.  
  20.             return;  
  21.         }  
  22.   
  23.         synchronized (installedApk) {  
  24.             String apkPath = applicationInfo.sourceDir;  
  25.             // installedApk的类型是:Set<String>。  
  26.             // 如果这个apk已经安装,则不重复安装。  
  27.             if (installedApk.contains(apkPath)) {  
  28.                 return;  
  29.             }  
  30.             installedApk.add(apkPath);  
  31.   
  32.             ......  
  33.   
  34.             // 类加载器应该直接或间接继承于BaseDexClassLoader。  
  35.             // 修改BaseDexClassLoader类中的DexPathList pathList字段,追加额外的DEX文件项。  
  36.             /* The patched class loader is expected to be a descendant of 
  37.              * dalvik.system.BaseDexClassLoader. We modify its 
  38.              * dalvik.system.DexPathList pathList field to append additional DEX 
  39.              * file entries. 
  40.              */  
  41.             ClassLoader loader;  
  42.   
  43.             ......  
  44.   
  45.             // dex将会输出到SECONDARY_FOLDER_NAME目录。  
  46.             File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);  
  47.             List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);  
  48.             // 校验这些zip文件是否合法。  
  49.             if (checkValidZipFiles(files)) {  
  50.                 // 安装提取出来的zip文件。  
  51.                 installSecondaryDexes(loader, dexDir, files);  
  52.             } else {  
  53.                 Log.w(TAG, "Files were not valid zip files.  Forcing a reload.");  
  54.                 // 最后一个参数是true,代表强制加载。  
  55.                 // Try again, but this time force a reload of the zip file.  
  56.                 files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);  
  57.   
  58.                 // 校验这些zip文件是否合法。  
  59.                 if (checkValidZipFiles(files)) {  
  60.                     // 安装提取出来的zip文件。  
  61.                     installSecondaryDexes(loader, dexDir, files);  
  62.                 } else {  
  63.                     // Second time didn't work, give up  
  64.                     throw new RuntimeException("Zip files were not valid.");  
  65.                 }  
  66.             }  
  67.         }  
  68.   
  69.     } catch (Exception e) {  
  70.         Log.e(TAG, "Multidex installation failure", e);  
  71.         throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");  
  72.     }  
  73.     Log.i(TAG, "install done");  
  74. }  

MultiDexExtractor.load获得/导出apk中多出的dex,这些dex导出后会被打包成zip文件:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 提取/获得apk中多dex的提取zip文件。 
  3.  * 如果不是加载已经存在的文件的情况,则还要保存apk的信息:时间戳、crc值、apk中dex的总个数。 
  4.  *  
  5.  * Extracts application secondary dexes into files in the application data 
  6.  * directory. 
  7.  * 
  8.  * @return a list of files that were created. The list may be empty if there 
  9.  *         are no secondary dex files. 
  10.  * @throws IOException if encounters a problem while reading or writing 
  11.  *         secondary dex files 
  12.  */  
  13. static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,  
  14.         boolean forceReload) throws IOException {  
  15.     Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");  
  16.     final File sourceApk = new File(applicationInfo.sourceDir);  
  17.   
  18.     long currentCrc = getZipCrc(sourceApk);  
  19.   
  20.     List<File> files;  
  21.     // isModified方法判断apk是否被修改过。  
  22.     if (!forceReload && !isModified(context, sourceApk, currentCrc)) {  
  23.         try {  
  24.             // 加载已经存在的文件,如果有的文件不存在,或者不是zip文件,则会抛出异常。  
  25.             files = loadExistingExtractions(context, sourceApk, dexDir);  
  26.         } catch (IOException ioe) {  
  27.             Log.w(TAG, "Failed to reload existing extracted secondary dex files,"  
  28.                     + " falling back to fresh extraction", ioe);  
  29.             // 从apk中提取出多dex,然后将这些dex逐个打包为zip文件,最终返回提取出来的zip文件列表。  
  30.             files = performExtractions(sourceApk, dexDir);  
  31.             // getTimeStamp方法中调用的是sourceApk.lastModified()方法。  
  32.             // putStoredApkInfo方法存储apk的信息:时间戳、crc值、apk中dex的总个数。  
  33.             putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);  
  34.   
  35.         }  
  36.     } else {  
  37.         Log.i(TAG, "Detected that extraction must be performed.");  
  38.         // 这里的performExtractions和putStoredApkInfo同上。  
  39.         files = performExtractions(sourceApk, dexDir);  
  40.         putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);  
  41.     }  
  42.   
  43.     Log.i(TAG, "load found " + files.size() + " secondary dex files");  
  44.     return files;  
  45. }  

MultiDexExtractor.performExtractions方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 从apk中提取出多dex,然后将这些dex逐个打包为zip文件。 
  3.  * @param sourceApk apk文件。 
  4.  * @param dexDir 输出目录。 
  5.  * @return 提取出来的zip文件列表。 
  6.  */  
  7. private static List<File> performExtractions(File sourceApk, File dexDir)  
  8.         throws IOException {  
  9.   
  10.     final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;  
  11.   
  12.     // 如果文件没有正确的前缀,则删除。  
  13.     // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that  
  14.     // contains a secondary dex file in there is not consistent with the latest apk.  Otherwise,  
  15.     // multi-process race conditions can cause a crash loop where one process deletes the zip  
  16.     // while another had created it.  
  17.     prepareDexDir(dexDir, extractedFilePrefix);  
  18.   
  19.     List<File> files = new ArrayList<File>();  
  20.   
  21.     final ZipFile apk = new ZipFile(sourceApk);  
  22.     try {  
  23.   
  24.         int secondaryNumber = 2;  
  25.   
  26.         ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);  
  27.         while (dexFile != null) {  
  28.             // 输出的文件名。  
  29.             String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;  
  30.             // 输出的文件。  
  31.             File extractedFile = new File(dexDir, fileName);  
  32.             files.add(extractedFile);  
  33.   
  34.             Log.i(TAG, "Extraction is needed for file " + extractedFile);  
  35.             int numAttempts = 0;  
  36.             boolean isExtractionSuccessful = false;  
  37.             while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {  
  38.                 numAttempts++;  
  39.   
  40.                 // 提取apk中的多dex文件,然后打包成一个zip文件。  
  41.                 // Create a zip file (extractedFile) containing only the secondary dex file  
  42.                 // (dexFile) from the apk.  
  43.                 extract(apk, dexFile, extractedFile, extractedFilePrefix);  
  44.   
  45.                 // 验证提取的文件是否是一个zip文件。  
  46.                 // Verify that the extracted file is indeed a zip file.  
  47.                 isExtractionSuccessful = verifyZipFile(extractedFile);  
  48.   
  49.                 // Log the sha1 of the extracted zip file  
  50.                 Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +  
  51.                         " - length " + extractedFile.getAbsolutePath() + ": " +  
  52.                         extractedFile.length());  
  53.                 if (!isExtractionSuccessful) {  
  54.                     // Delete the extracted file  
  55.                     extractedFile.delete();  
  56.                     if (extractedFile.exists()) {  
  57.                         Log.w(TAG, "Failed to delete corrupted secondary dex '" +  
  58.                                 extractedFile.getPath() + "'");  
  59.                     }  
  60.                 }  
  61.             }  
  62.             if (!isExtractionSuccessful) {  
  63.                 throw new IOException("Could not create zip file " +  
  64.                         extractedFile.getAbsolutePath() + " for secondary dex (" +  
  65.                         secondaryNumber + ")");  
  66.             }  
  67.             secondaryNumber++;  
  68.             dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);  
  69.         }  
  70.     } finally {  
  71.         try {  
  72.             apk.close();  
  73.         } catch (IOException e) {  
  74.             Log.w(TAG, "Failed to close resource", e);  
  75.         }  
  76.     }  
  77.   
  78.     return files;  
  79. }  

MultiDexExtractor.putStoredApkInfo方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private static void putStoredApkInfo(Context context, long timeStamp, long crc,  
  2.         int totalDexNumber) {  
  3.     SharedPreferences prefs = getMultiDexPreferences(context);  
  4.     SharedPreferences.Editor edit = prefs.edit();  
  5.     edit.putLong(KEY_TIME_STAMP, timeStamp);    // 时间戳  
  6.     edit.putLong(KEY_CRC, crc); // crc值。  
  7.     /* SharedPreferences.Editor doc says that apply() and commit() "atomically performs the 
  8.      * requested modifications" it should be OK to rely on saving the dex files number (getting 
  9.      * old number value would go along with old crc and time stamp). 
  10.      */  
  11.     edit.putInt(KEY_DEX_NUMBER, totalDexNumber);    // dex总个数。  
  12.     apply(edit);  
  13. }  

MultiDex.installSecondaryDexes方法,对dex进行安装:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)  
  2.         throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,  
  3.         InvocationTargetException, NoSuchMethodException, IOException {  
  4.     // 安装。  
  5.     if (!files.isEmpty()) {  
  6.         if (Build.VERSION.SDK_INT >= 19) {  
  7.             V19.install(loader, files, dexDir);  
  8.         } else if (Build.VERSION.SDK_INT >= 14) {  
  9.             V14.install(loader, files, dexDir);  
  10.         } else {  
  11.             V4.install(loader, files);  
  12.         }  
  13.     }  
  14. }  

这个方法的代码非常简单,挑选V19.install分析:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 安装多dex。 
  3.  * @param loader  
  4.  * @param additionalClassPathEntries zip文件列表,这些zip文件中都只有一个文件classes.dex。 
  5.  * @param optimizedDirectory 优化的dex存放的目录。 
  6.  */  
  7. private static void install(ClassLoader loader, List<File> additionalClassPathEntries,  
  8.         File optimizedDirectory)  
  9.                 throws IllegalArgumentException, IllegalAccessException,  
  10.                 NoSuchFieldException, InvocationTargetException, NoSuchMethodException {  
  11.     // 被打补丁的类加载器应该直接或间接继承BaseDexClassLoader。  
  12.     // 我们修改DexPathList pathList字段,追加额外的DEX文件项。  
  13.     /* The patched class loader is expected to be a descendant of 
  14.      * dalvik.system.BaseDexClassLoader. We modify its 
  15.      * dalvik.system.DexPathList pathList field to append additional DEX 
  16.      * file entries. 
  17.      */  
  18.     // dexPathList = load.pathList;  
  19.     Field pathListField = findField(loader, "pathList");  
  20.     Object dexPathList = pathListField.get(loader);  
  21.   
  22.     ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();  
  23.     // makeDexElements方法调用了DexPathList中的makeDexElements方法,这个方法可以加载并优化dex、zip、jar。  
  24.     // expandFieldArray方法将makeDexElements返回的数组patch到dexPathList.dexElements中。  
  25.     expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,  
  26.             new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,  
  27.             suppressedExceptions));  
  28.   
  29.     ......  
  30. }  

V19.makeDexElements方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * A wrapper around 
  3.  * {@code private static final dalvik.system.DexPathList#makeDexElements}. 
  4.  */  
  5. private static Object[] makeDexElements(  
  6.         Object dexPathList, ArrayList<File> files, File optimizedDirectory,  
  7.         ArrayList<IOException> suppressedExceptions)  
  8.                 throws IllegalAccessException, InvocationTargetException,  
  9.                 NoSuchMethodException {  
  10.     Method makeDexElements =  
  11.             findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,  
  12.                     ArrayList.class);  
  13.   
  14.     // return (DexPathList.Element[]) dexPathList.makeDexElements(files, optimizedDirectory, suppressedExceptions)  
  15.     return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,  
  16.             suppressedExceptions);  
  17. }  

MultiDex.expandFieldArray方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 原字段内容加上扩展的数组元素替换相应字段的内容,这个字段是一个数组。 
  3.  *  
  4.  * Replace the value of a field containing a non null array, by a new array containing the 
  5.  * elements of the original array plus the elements of extraElements. 
  6.  * @param instance the instance whose field is to be modified. 
  7.  * @param fieldName the field to modify. 
  8.  * @param extraElements elements to append at the end of the array. 
  9.  */  
  10. private static void expandFieldArray(Object instance, String fieldName,  
  11.         Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,  
  12.         IllegalAccessException {  
  13.     // combined = new <Type>[original.length + extraElements.length];  
  14.     Field jlrField = findField(instance, fieldName);  
  15.     Object[] original = (Object[]) jlrField.get(instance);  
  16.     Object[] combined = (Object[]) Array.newInstance(  
  17.             original.getClass().getComponentType(), original.length + extraElements.length);  
  18.   
  19.     System.arraycopy(original, 0, combined, 0, original.length);  
  20.     System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);  
  21.     // 替换对象中的数组字段。  
  22.     jlrField.set(instance, combined);  
  23. }  

0 0