隨著app的迭代祭埂,app的功能越來越多,導(dǎo)致app的大小也相應(yīng)增大兵钮,很多app已經(jīng)超過了100M蛆橡,成了超級app。但安卓的方法數(shù)有一個(gè)限制掘譬,不能超過65536泰演,針對這個(gè)問題,google給出了解決方案葱轩,超過后睦焕,可以進(jìn)行分包解決藐握。
使用也很簡單,直接調(diào)用MultiDex.install(this)就可以垃喊。
基本原理是jvm在方法區(qū)加載class文件猾普,下次使用時(shí),如果加載過了本谜,就可以直接用來使用初家。而在查找類時(shí),會從classloader里面pathList里面查找乌助,遍歷pathList里面的dexElements集合溜在,找到后就直接返回。
所以我們只需要把分dex的dexElements加到主dex的dexElements里面就可以了他托。
好的掖肋,我們來看一下具體怎么實(shí)現(xiàn)
public class MultiDexApplication extends Application {
public MultiDexApplication() {
}
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
我們跟著源碼來一步步揭開它的面紗。
public final class MultiDex {
static {
//分dex的目錄赏参。打包的時(shí)候志笼,如果方法數(shù)超了,會生成class.dex,class1.dex,class2.dex文件
SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
installedApk = new HashSet();
//系統(tǒng)是否支持分包
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;
//已經(jīng)處理過來把篓,不需要再處理
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<File> 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<File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
if (!files.isEmpty()) {
//根據(jù)系統(tǒng)版本來安裝,我們看一下大于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<File> 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;
//首次進(jìn)入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);
//將提取的時(shí)間和分包的dex數(shù)量記錄到sp
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
return files;
}
private static List<File> performExtractions(File sourceApk, File dexDir) throws IOException {
String extractedFilePrefix = sourceApk.getName() + ".classes";
//刪除除去extractedFilePrefix的其它文件
prepareDexDir(dexDir, extractedFilePrefix);
List<File> 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<File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
//找到classloader里面的pathList變量皇耗,該變量是dex元素的集合
Field pathListField = MultiDex.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList();
//makeDexElements,根據(jù)dex文件列表揍很,生成對應(yīng)的dexElements郎楼,然后通過expandFieldArray方法,拼接到主dex的dexElements后面窒悔,這樣就將分包的dex跟主dex列表合并成一個(gè)來
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<File> files, File optimizedDirectory, ArrayList<IOException> 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));
}
}