MultiDex解析
MultiDex的工作流程具体分为两个部分,一个部分是打包构建Apk的时候,将Dex文件拆分成若干个小的Dex文件,这个Android Studio已经帮我们做了(设置 “multiDexEnabled true”),另一部分就是在启动Apk的时候,同时加载多个Dex文件(具体是加载Dex文件优化后的Odex文件,不过文件名还是.dex),这一部分工作从Android 5.0开始系统已经帮我们做了,但是在Android 5.0以前还是需要通过MultiDex Support库来支持(MultiDex.install(Context))。
这里就来分析一下加载多个dex文件的过程:
1:dex如何分包:
在build.gradle里面设置:
defaultConfig { multiDexEnabled true }
自定义的application继承MultiDexApplication。
public class MyApplication extends MultiDexApplication { .............. }
2:MultiDexApplicatioin代码如何加载:
public class MultiDexApplication extends Application { public MultiDexApplication() { } protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
1:MultiDex.install(this);
public static void install(Context context) { Log.i("MultiDex", "Installing application"); if (IS_VM_MULTIDEX_CAPABLE) { Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled."); } else if (VERSION.SDK_INT < 4) { throw new RuntimeException("MultiDex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + "."); } else { try { ApplicationInfo applicationInfo = getApplicationInfo(context); if (applicationInfo == null) { Log.i("MultiDex", "No ApplicationInfo available, i.e. running on a test Context: MultiDex support library is disabled."); return; } // applicationInfo.sourceDir:获取应用存放数据目录 /data/app/package-2/base.apk
// applicationInfo.dataDir: 存放数据的路径 应用数据目录 /data/data/com.example.admin.frameworkdemo doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes", "", true); } catch (Exception var2) { Log.e("MultiDex", "MultiDex installation failure", var2); throw new RuntimeException("MultiDex installation failed (" + var2.getMessage() + ")."); } Log.i("MultiDex", "install done"); } }
2:doInstallation:
private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix, boolean reinstallOnPatchRecoverableException) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException { synchronized(installedApk) {
// 检查是否已经install过了,避免重复install if (!installedApk.contains(sourceApk)) { installedApk.add(sourceApk); if (VERSION.SDK_INT > 20) { Log.w("MultiDex", "MultiDex is not guaranteed to work in SDK version " + VERSION.SDK_INT + ": SDK version higher than " + 20 + " should be backed by " + "runtime with built-in multidex capabilty but it's not the " + "case here: java.vm.version=\"" + System.getProperty("java.vm.version") + "\""); } ClassLoader loader; try {
// 获取到的是一个PathClassloader loader = mainContext.getClassLoader(); } catch (RuntimeException var25) { Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", var25); return; } if (loader == null) { Log.e("MultiDex", "Context class loader is null. Must be running in test mode. Skip patching."); } else { try { clearOldDexDir(mainContext); } catch (Throwable var24) { Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", var24); } // 构造存放classes.dex
的目录/data/data/package/code_cache/secondary-dexes
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName); MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir); IOException closeException = null; // 将
apk
中的classes.dex
提取并存放为data/data/package/code_cache/secondary-dexes/package--1.apk.classN.zip
,以便后续加载到内存中。 try { List files = extractor.load(mainContext, prefsKeyPrefix, false); try {
// 加载 installSecondaryDexes(loader, dexDir, files); } catch (IOException var26) { if (!reinstallOnPatchRecoverableException) { throw var26; } Log.w("MultiDex", "Failed to install extracted secondary dex files, retrying with forced extraction", var26); files = extractor.load(mainContext, prefsKeyPrefix, true); installSecondaryDexes(loader, dexDir, files); } } finally { try { extractor.close(); } catch (IOException var23) { closeException = var23; } } if (closeException != null) { throw closeException; } } } } }
3:getDexDir:
// 构造存放classes.dex
的目录/data/data/package/code_cache/secondary-dexes
其中dataDir为: /data/data/package
secondaryFolderName为前面传进来的
"secondary-dexes"
private static File getDexDir(Context context, File dataDir, String secondaryFolderName) throws IOException { File cache = new File(dataDir, "code_cache"); try { mkdirChecked(cache); } catch (IOException var5) { cache = new File(context.getFilesDir(), "code_cache"); mkdirChecked(cache); }
///data/data/package/code_cache/secondary-dexes
File dexDir = new File(cache, secondaryFolderName); mkdirChecked(dexDir); return dexDir; }
4:extractor.load:
// 将apk
中的classes.dex
提取并存放为data/data/package/code_cache/secondary-dexes/package--1.apk.classN.zip
,以便后续加载到内存中。
List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException { Log.i("MultiDex", "MultiDexExtractor.load(" + this.sourceApk.getPath() + ", " + forceReload + ", " + prefsKeyPrefix + ")"); if (!this.cacheLock.isValid()) { throw new IllegalStateException("MultiDexExtractor was closed"); } else { List files; if (!forceReload && !isModified(context, this.sourceApk, this.sourceCrc, prefsKeyPrefix)) { try { files = this.loadExistingExtractions(context, prefsKeyPrefix); } catch (IOException var6) { Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var6); files = this.performExtractions(); putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files); } } else { if (forceReload) { Log.i("MultiDex", "Forced extraction must be performed."); } else { Log.i("MultiDex", "Detected that extraction must be performed."); } files = this.performExtractions(); putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files); } Log.i("MultiDex", "load found " + files.size() + " secondary dex files"); return files; } }
5:解析performExtractions:
private List<MultiDexExtractor.ExtractedDex> performExtractions() throws IOException {
// extractedFilePrefix = package-2.apk.classes
String extractedFilePrefix = this.sourceApk.getName() + ".classes";
this.clearDexDir();
List<MultiDexExtractor.ExtractedDex> files = new ArrayList();
// 获取apk的zip对象
ZipFile apk = new ZipFile(this.sourceApk);
try {
int secondaryNumber = 2;
// 这里的逻辑就是解压apk,遍历出里面的dex文件,例如class1.dex,class2.dex,然后又压缩成class1.zip, class2.zip...,然后返回zip文件列表
for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
// fileName = package-2.apk.classes2.zip
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
// dexDir:/data/data/package/code_cache/secondary-dexes
MultiDexExtractor.ExtractedDex extractedFile = new MultiDexExtractor.ExtractedDex(this.dexDir, fileName);
// extractedFile:/data/data/package/code_cache/secondary-dexes/package-2.apk.classes2.zip
files.add(extractedFile);
Log.i("MultiDex", "Extraction is needed for file " + extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
while(numAttempts < 3 && !isExtractionSuccessful) {
++numAttempts;
// 将classes1.dex文件写到压缩文件classes.zip中,做多重试三次
extract(apk, dexFile, extractedFile, extractedFilePrefix);
try {
extractedFile.crc = getZipCrc(extractedFile);
isExtractionSuccessful = true;
} catch (IOException var18) {
isExtractionSuccessful = false;
Log.w("MultiDex", "Failed to read crc from " + extractedFile.getAbsolutePath(), var18);
}
Log.i("MultiDex", "Extraction " + (isExtractionSuccessful ? "succeeded" : "failed") + " '" + extractedFile.getAbsolutePath() + "': length " + extractedFile.length() + " - crc: " + extractedFile.crc);
if (!isExtractionSuccessful) {
extractedFile.delete();
if (extractedFile.exists()) {
Log.w("MultiDex", "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;
}
} finally {
try {
apk.close();
} catch (IOException var17) {
Log.w("MultiDex", "Failed to close resource", var17);
}
}
// 返回dex的压缩文件列表
return files;
}
6:接上面的第2步骤里面的installSecondaryDexes
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException, SecurityException, ClassNotFoundException, InstantiationException { if (!files.isEmpty()) { if (VERSION.SDK_INT >= 19) { MultiDex.V19.install(loader, files, dexDir); } else if (VERSION.SDK_INT >= 14) { MultiDex.V14.install(loader, files); } else { MultiDex.V4.install(loader, files); } } }
install:将每一个.zip
转化为DexPathList.Element
,添加到DexPathList
的elements
字段中(参考:https://www.cnblogs.com/renhui/p/11716975.html)
就是创建一个新的数组,把原来数组内容(主dex)和要增加的内容(dex2、dex3...)拷贝进去,反射替换原来的dexElements为新的数组
Tinker热修复的原理也是通过反射将修复后的dex添加到这个dex数组去,不同的是热修复是添加到数组最前面,而MultiDex是添加到数组后面。
static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
// dexDir:/data/data/package/code_cache/secondary-dexes
// 传递的loader是PathClassLoader,findFidld()方法找到父类BaseClassLoader中pathList属性
Field pathListField = MultiDex.findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList<IOException> suppressedExceptions = new ArrayList();
// 将dex文件添加到DexPathList中的dexElements中数组的末尾 MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); if (suppressedExceptions.size() > 0) { Iterator var6 = suppressedExceptions.iterator(); while(var6.hasNext()) { IOException e = (IOException)var6.next(); Log.w("MultiDex", "Exception in makeDexElement", e); } Field suppressedExceptionsField = MultiDex.findField(dexPathList, "dexElementsSuppressedExceptions"); IOException[] dexElementsSuppressedExceptions = (IOException[])((IOException[])suppressedExceptionsField.get(dexPathList)); if (dexElementsSuppressedExceptions == null) { dexElementsSuppressedExceptions = (IOException[])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); IOException exception = new IOException("I/O exception during makeDexElement"); exception.initCause((Throwable)suppressedExceptions.get(0)); throw exception; } }
7:综上所诉,大致的流程如下:红色部分为耗时较大的地方(转自:https://blog.csdn.net/earbao/article/details/70189721)