defaultConfig{
multiDexEnabled true
}
複製代碼
implementation 'com.android.support:multidex:1.0.3'
複製代碼
MultiDex.install(this);
複製代碼
com/alex/kotlin/jniapplication/MainActivity.class
com/alex/kotlin/jniapplication/MainActivity2.class
複製代碼
multiDexKeepFile file('multidex-config.txt') 複製代碼
public static void install(Context context) {
if (IS_VM_MULTIDEX_CAPABLE) {
} else if (VERSION.SDK_INT < 4) { //Multidex 最小支持版本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); //獲取程序的ApplicationInfo
//分別傳入apk安裝文件和apk的信息文件
doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes", "", true);
} catch (Exception var2) {}
}
}
複製代碼
private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix, boolean reinstallOnPatchRecoverableException) {
synchronized(installedApk) {
if (!installedApk.contains(sourceApk)) { // 判讀次APk是否被加載過,加載過會自動跳過
installedApk.add(sourceApk); // 靜態集合保存被加載的apk
ClassLoader loader;
try {
loader = mainContext.getClassLoader(); //獲取系統的ClassLoader實例
} catch (RuntimeException var25) {}
clearOldDexDir(mainContext); //清除舊的加載信息
//在dataDir文件夾下建立加載dex文件的緩存目錄 code_cache/secondary-dexes
//若是dataDir建立失敗會建立getFilesDir()爲緩存目錄
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
//建立MultiDexExtractor實例,保存源文件目錄和緩存目錄
try {
List files = extractor.load(mainContext, prefsKeyPrefix, false); //(1)加載dex文件
try {
installSecondaryDexes(loader, dexDir, files); //(2)安裝dex文件
} catch (IOException var26) {
files = extractor.load(mainContext, prefsKeyPrefix, true); //出現異常會強制從新加載,參數true
installSecondaryDexes(loader, dexDir, files);
}
} finally {
try {
extractor.close();
} catch (IOException var23) {
closeException = var23;
}
}
}
}
複製代碼
總結:java
List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException {
List files;
// 判斷是否強制從新加載 或 Dex文件是否有改變
if (!forceReload && !isModified(context, this.sourceApk, this.sourceCrc, prefsKeyPrefix)) {
try {
files = this.loadExistingExtractions(context, prefsKeyPrefix);//未強制和未改變的直接從緩存的dex中加載
} catch (IOException var6) {
files = this.performExtractions();
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
}
} else {
files = this.performExtractions(); //強制從新加載
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files); //緩存信息
}
return files;
}
複製代碼
總結:android
若是沒有強制從新加載、文件存在且沒有變化則從緩存的dex中加載數組
未加載過的執行加載程序並緩存信息緩存
performExtractions()app
private List<MultiDexExtractor.ExtractedDex> performExtractions() throws IOException {
String extractedFilePrefix = this.sourceApk.getName() + ".classes」; //設置文件名前綴
this.clearDexDir();
List<MultiDexExtractor.ExtractedDex> files = new ArrayList();
ZipFile apk = new ZipFile(this.sourceApk); //將傳入的APK文件轉換爲Zip壓縮文件
try {
int secondaryNumber = 2; //排除主Dex從第二個dex中開始遍歷加載
for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) { //循環獲取apk文件夾下的dex文件
String fileName = extractedFilePrefix + secondaryNumber + ".zip」; //每一個dex文件生成對應的zip文件
//對每一個dex文件都建立一個文件extractedFile並保存到集合中
MultiDexExtractor.ExtractedDex extractedFile = new MultiDexExtractor.ExtractedDex(this.dexDir, fileName);
files.add(extractedFile);
int numAttempts = 0; //統計加載次數,默認加載3次,超過3次還不成功則加載失敗
boolean isExtractionSuccessful = false; //標記是否加載成功
while(numAttempts < 3 && !isExtractionSuccessful) { //循環執行加載
++numAttempts;
extract(apk, dexFile, extractedFile, extractedFilePrefix); //真正執行加載的方法
try {
extractedFile.crc = getZipCrc(extractedFile); //加載成功
isExtractionSuccessful = true;
} catch (IOException var18) {
}
}
++secondaryNumber; //繼續加載下一個dex文件
}
} finally {
}
return files;
}
複製代碼
上述代碼執行邏輯:ide
將傳入的APK文件轉換爲Zip壓縮文件,則每一個dex文件都是Zip下的子文件源碼分析
排除主Dex從第二個dex中開始遍歷加載,並根據secondaryNumber獲取Zip下個每一個dex文件,而後在code_cache/secondary-dexes目錄下爲每一個dex建立zip文件gradle
對每一個dex文件都建立一個文件extractedFile並保存到集合中ui
執行extract()進行文件加載this
extract():執行文件的寫入,將apk中除了主dex以外的dex文件逐個讀取寫入到zip文件中
private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) throws IOException, FileNotFoundException {
InputStream in = apk.getInputStream(dexFile); //從dexFile中獲取字節流
ZipOutputStream out = null;
//在緩存文件夾目錄中建立臨時文件夾 tmp-***.zip
File tmp = File.createTempFile("tmp-" + extractedFilePrefix, ".zip", extractTo.getParentFile());
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp))); //建立ZipOutputStream輸出流
try {
ZipEntry classesDex = new ZipEntry("classes.dex");
classesDex.setTime(dexFile.getTime());
out.putNextEntry(classesDex); //設置Zip文件
byte[] buffer = new byte[16384]; //讀取dexFile中的字節流寫入臨時文件tem中保存在緩存文件夾下
for(int length = in.read(buffer); length != -1; length = in.read(buffer)) {
out.write(buffer, 0, length); //將數據寫入名爲classes.dex的Zip文件
}
out.closeEntry();
} finally {
out.close();
}
} finally {
closeQuietly(in);
tmp.delete();
}
}
複製代碼
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files){
if (!files.isEmpty()) { //文件是否爲null
if (VERSION.SDK_INT >= 19) {
MultiDex.V19.install(loader, files, dexDir); // 根據SDK不一樣執行不一樣的install邏輯,從緩存文件中加載
} else if (VERSION.SDK_INT >= 14) {
MultiDex.V14.install(loader, files);
} else {
MultiDex.V4.install(loader, files);
}
}
}
複製代碼
static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) {
Field pathListField = MultiDex.findField(loader, "pathList」); //反射調用ClassLoader的pathList
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList();
//將取出的zip文件包裝成Element對象並擴招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) {
//調用dexPathList中的makeDexElements方法傳入文件集合,解析zip文件集合中的dex文件保存爲Elements
Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
//執行方法
return (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
}
複製代碼
上面主要是使用ClassLoader加載文件,關於ClassLoader不熟悉的點擊Android熱修復之路(一)——ClassLoader查看,上述代碼的執行邏輯:
關於MutilDex的加載原理就到此結束了,簡單來講就是將全部的dex都加載在Element數組中,而後擴展只ClassLoader的pathList中,在ClassLoader查找類時就能夠找到了;