随着app的迭代,app的功能越来越多,导致app的大小也相应增大,很多app已经超过了100M,成了超级app。但安卓的方法数有一个限制,不能超过65536,针对这个问题,google给出了解决方案,超过后,可以进行分包解决。
使用也很简单,直接调用MultiDex.install(this)就可以。
基本原理是jvm在方法区加载class文件,下次使用时,如果加载过了,就可以直接用来使用。而在查找类时,会从classloader里面pathList里面查找,遍历pathList里面的dexElements集合,找到后就直接返回。
所以我们只需要把分dex的dexElements加到主dex的dexElements里面就可以了。
好的,我们来看一下具体怎么实现
public class MultiDexApplication extends Application {
public MultiDexApplication() {
}
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
我们跟着源码来一步步揭开它的面纱。
public final class MultiDex {
static {
//分dex的目录。打包的时候,如果方法数超了,会生成class.dex,class1.dex,class2.dex文件
SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
installedApk = new HashSet();
//系统是否支持分包
IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version"));
}
public static void install(Context context) {
Log.i("MultiDex", "install");
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("Multi dex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
} else {
try {
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
return;
}
Set var2 = installedApk;
synchronized(installedApk) {
String apkPath = applicationInfo.sourceDir;
//已经处理过来,不需要再处理
if (installedApk.contains(apkPath)) {
return;
}
installedApk.add(apkPath);
ClassLoader loader;
try {
loader = context.getClassLoader();
} catch (RuntimeException var9) {
return;
}
try {
//清空dex文件
clearOldDexDir(context);
} catch (Throwable var8) {
}
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
//将分包的dex文件提取出来,files是文件列表
List files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
if (checkValidZipFiles(files)) {
//安装分的dex
installSecondaryDexes(loader, dexDir, files);
} else {
files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
if (!checkValidZipFiles(files)) {
throw new RuntimeException("Zip files were not valid.");
}
installSecondaryDexes(loader, dexDir, files);
}
}
} catch (Exception var11) {
Log.e("MultiDex", "Multidex installation failure", var11);
throw new RuntimeException("Multi dex installation failed (" + var11.getMessage() + ").");
}
Log.i("MultiDex", "install done");
}
}
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
if (!files.isEmpty()) {
//根据系统版本来安装,我们看一下大于19的
if (VERSION.SDK_INT >= 19) {
MultiDex.V19.install(loader, files, dexDir);
} else if (VERSION.SDK_INT >= 14) {
MultiDex.V14.install(loader, files, dexDir);
} else {
MultiDex.V4.install(loader, files);
}
}
}
}
final class MultiDexExtractor {
static List load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload) throws IOException {
Log.i("MultiDex", "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");
File sourceApk = new File(applicationInfo.sourceDir);
long currentCrc = getZipCrc(sourceApk);
List files;
//首次进入else
if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
try {
files = loadExistingExtractions(context, sourceApk, dexDir);
} catch (IOException var9) {
Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var9);
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
} else {
//提取dex文件
files = performExtractions(sourceApk, dexDir);
//将提取的时间和分包的dex数量记录到sp
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
return files;
}
private static List performExtractions(File sourceApk, File dexDir) throws IOException {
String extractedFilePrefix = sourceApk.getName() + ".classes";
//删除除去extractedFilePrefix的其它文件
prepareDexDir(dexDir, extractedFilePrefix);
List files = new ArrayList();
ZipFile apk = new ZipFile(sourceApk);
try {
//分的dex的后缀是从2开始的
int secondaryNumber = 2;
for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
File extractedFile = new File(dexDir, fileName);
files.add(extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
//提取dex文件可能失败,尝试3次
while(numAttempts < 3 && !isExtractionSuccessful) {
++numAttempts;
//将dexFile提取成extractedFile
extract(apk, dexFile, extractedFile, extractedFilePrefix);
isExtractionSuccessful = verifyZipFile(extractedFile);
Log.i("MultiDex", "Extraction " + (isExtractionSuccessful ? "success" : "failed") + " - length " + extractedFile.getAbsolutePath() + ": " + extractedFile.length());
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 var16) {
Log.w("MultiDex", "Failed to close resource", var16);
}
}
return files;
}
}
private static final class V19 {
private V19() {
}
private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
//找到classloader里面的pathList变量,该变量是dex元素的集合
Field pathListField = MultiDex.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList suppressedExceptions = new ArrayList();
//makeDexElements,根据dex文件列表,生成对应的dexElements,然后通过expandFieldArray方法,拼接到主dex的dexElements后面,这样就将分包的dex跟主dex列表合并成一个来
MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
if (suppressedExceptions.size() > 0) {
Iterator i$ = suppressedExceptions.iterator();
while(i$.hasNext()) {
IOException e = (IOException)i$.next();
Log.w("MultiDex", "Exception in makeDexElement", e);
}
Field suppressedExceptionsField = MultiDex.findField(loader, "dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions = (IOException[])((IOException[])suppressedExceptionsField.get(loader));
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(loader, dexElementsSuppressedExceptions);
}
}
private static Object[] makeDexElements(Object dexPathList, ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
return (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
}
}