Multidex详解
1.使用
1.在项目gralde配置中增加依赖 compile "com.android.support:multidex:1.0.2"
2.在AndroidManifest.xml中声明 application为MultiDexApplication,
或者在自定义的application中,重写如下方法
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
android.support.multidex.MultiDex.install(this);
}
2.源码详解
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;
}
doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes", "");
} catch (Exception var2) {
Log.e("MultiDex", "MultiDex installation failure", var2);
throw new RuntimeException("MultiDex installation failed (" + var2.getMessage() + ").");
}
Log.i("MultiDex", "install done");
}
}
private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
Set var5 = installedApk;
synchronized(installedApk) {
if(!installedApk.contains(sourceApk)) {
installedApk.add(sourceApk);
....
ClassLoader loader;
try {
loader = mainContext.getClassLoader();
} catch (RuntimeException var11) {
Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", var11);
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 var10) {
Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", var10);
}
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
List<? extends File> files = MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false);
installSecondaryDexes(loader, dexDir, files);
}
}
}
}
3.加载过程
static List<? extends File> load(Context context, File sourceApk, File dexDir, String prefsKeyPrefix, boolean forceReload) throws IOException {
...
if(!forceReload && !isModified(context, sourceApk, currentCrc, prefsKeyPrefix)) {
try {
files = loadExistingExtractions(context, sourceApk, dexDir, prefsKeyPrefix);
} catch (IOException var21) {
Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var21);
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, files);
}
} else {
Log.i("MultiDex", "Detected that extraction must be performed.");
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, files);
}
...
}
private static List<ExtractedDex> performExtractions(File sourceApk, File dexDir) throws IOException {
....
int secondaryNumber = 2;
for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" +secondaryNumber + ".dex")) {
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName);
files.add(extractedFile);
Log.i("MultiDex", "Extraction is needed for file " + extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
while(numAttempts < 3 && !isExtractionSuccessful) {
++numAttempts;
extract(apk, dexFile, extractedFile, extractedFilePrefix);
try {
extractedFile.crc = getZipCrc(extractedFile);
isExtractionSuccessful = true;
} catch (IOException var19) {
isExtractionSuccessful = false;
Log.w("MultiDex", "Failed to read crc from " + extractedFile.getAbsolutePath(), var19);
}
Log.i("MultiDex", "Extraction " + (isExtractionSuccessful?"succeeded":"failed") + " - length " + extractedFile.getAbsolutePath() + ": " + 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;
}
...
}
private static void putStoredApkInfo(Context context, String keyPrefix, long timeStamp, long crc, List<ExtractedDex> extractedDexes) {
SharedPreferences prefs = getMultiDexPreferences(context);
Editor edit = prefs.edit();
edit.putLong(keyPrefix + "timestamp", timeStamp);
edit.putLong(keyPrefix + "crc", crc);
edit.putInt(keyPrefix + "dex.number", extractedDexes.size() + 1);
int extractedDexId = 2;
for(Iterator var10 = extractedDexes.iterator(); var10.hasNext(); ++extractedDexId) {
ExtractedDex dex = (ExtractedDex)var10.next();
edit.putLong(keyPrefix + "dex.crc." + extractedDexId, dex.crc);
edit.putLong(keyPrefix + "dex.time." + extractedDexId, dex.lastModified());
}
edit.commit();
}
4.合并dex
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
if(!files.isEmpty()) {
if(VERSION.SDK_INT >= 19) {
V19.install(loader, files, dexDir);
} else if(VERSION.SDK_INT >= 14) {
V14.install(loader, files, dexDir);
} else {
V4.install(loader, files);
}
}
}
这里先说明下原理:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
private static final class V19 {
private V19() {
}
private static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
Field pathListField = MultiDex.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList();
合并dex的dexElements到pathClassLoade的dexElements中, 通过反射实现
MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
...
}
private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", new Class[]{ArrayList.class, File.class, ArrayList.class});
return (Object[])((Object[])makeDexElements.invoke(dexPathList, new Object[]{files, optimizedDirectory, suppressedExceptions}));
}
}