官方介绍:
Configure Apps with Over 64K Methods
首先看一下官方的使用介绍:
When writing instrumentation tests for multidex apps, no additional configuration is required. AndroidJUnitRunner supports multidex out of the box, as long as you use MultiDexApplication or override the attachBaseContext() method in your custom Application object and call MultiDex.install(this) to enable multidex.
如果我们需要使用android_support_multidex这个jar包,只需要我们使用jar包中的MultiDexApplication或者重写attachBaseContext()
方法即可,即:
attachBaseContext
方法,调用MultiDex.install(this)
。下面我们看一下这个方法中具体的实现:
public class MultiDexApplication
extends Application
{
protected void attachBaseContext(Context base)
{
super.attachBaseContext(base);
MultiDex.install(this); //实际上执行了MultiDex.install(this)方法
}
}
在这个方法中,首先判断了一下安卓设备是否支持MultiDex,实际上Android4.4以上的版本的虚拟机引入了ART技术,已经支持MultiDex,与Dalvik的区别详见:ART and Dalvik。
接下来判断了当前Android系统版本,我们可以看到,android_support_multidex.jar
实际上支持的范围是Api: 4到20
继续向下调用MultiDexExtractor.load(context, applicationInfo, dexDir, false);
方法
public static void install(Context context)
{
Log.i("MultiDex", "install");
if (IS_VM_MULTIDEX_CAPABLE) //判断安卓设备是否支持MultiDex,支持则直接return
{
Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
return;
}
if (Build.VERSION.SDK_INT < 4) { //判断Android设备的系统版本,最小只支持Api 4(Android 1.6)
throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT +
" is unsupported. Min SDK version is " + 4 + ".");
}
try
{
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) { //判断Application是否为null,为null表示未调用install方法。
return;
}
synchronized (installedApk)
{
String apkPath = applicationInfo.sourceDir; //获得当前apk的路径,判断是否已经安装过,安装过则不再次进行安装
if (installedApk.contains(apkPath)) {
return;
}
installedApk.add(apkPath);
if (Build.VERSION.SDK_INT > 20) { //判断Android设备的系统版本,Api大于20(Android 4.4W)默认支持Multidex
Log.w("MultiDex", "MultiDex is not guaranteed to work in SDK version " +
Build.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") + "\"");
}
try
{
loader = context.getClassLoader(); //获取当前的ClassLoader
}
catch (RuntimeException e)
{
ClassLoader loader;
Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.",
e); return;
}
ClassLoader loader;
if (loader == null)
{
Log.e("MultiDex",
"Context class loader is null. Must be running in test mode. Skip patching.");
return;
}
try
{
clearOldDexDir(context); //清空旧的Dex目录
}
catch (Throwable t)
{
Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.",
t);
}
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME); //从Dex文件夹路径: /data/app/包名/code_cache/secondary-dexes
List files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); //获取从Dex文件的集合
if (checkValidZipFiles(files)) //判断是否是正常的zip文件
{
installSecondaryDexes(loader, dexDir, files); //开始加载从Dex文件,重要!
}
else
{
Log.w("MultiDex", "Files were not valid zip files. Forcing a reload.");
files = MultiDexExtractor.load(context, applicationInfo, dexDir, true); //zip格式有误的话,则重新强制再次加载
if (checkValidZipFiles(files)) {
installSecondaryDexes(loader, dexDir, files); //第二次判断,如果格式有误,最后一次强制加载
} else {
throw new RuntimeException("Zip files were not valid.");
}
}
}
Log.i("MultiDex", "install done");
}
catch (Exception e)
{
Log.e("MultiDex", "Multidex installation failure", e);
throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");
}
}
MultiDexExtractor.load(context, applicationInfo, dexDir, false);
方法:
下面我们来看一下,这个方法中具体是怎么实现的:
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); //获得这个apk文件安装路径 /data/app/包名-1.apk
long currentCrc = getZipCrc(sourceApk); //进行crc校验
List<File> files;
if ((!forceReload) && (!isModified(context, sourceApk, currentCrc))) //判断是否再次加载从dex以及是否被修改过
{
try
{
files = loadExistingExtractions(context, sourceApk, dexDir);
}
catch (IOException ioe)
{
List<File> files;
Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction",
ioe);
List<File> files = performExtractions(sourceApk, dexDir); //读取异常时候调用
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
}
else
{
Log.i("MultiDex", "Detected that extraction must be performed.");
files = performExtractions(sourceApk, dexDir); //强制重新加载时则会调用该方法
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
Log.i("MultiDex", "load found " + files.size() + " secondary dex files");
return files;
}
loadExistingExtractions(context, sourceApk, dexDir);
获得已经加载的包含Dex文件的压缩文件:
private static List loadExistingExtractions(Context context, File sourceApk, File dexDir)
throws IOException
{
Log.i("MultiDex", "loading existing secondary dex files");
String extractedFilePrefix = sourceApk.getName() + ".classes"; // /data/app/包名-1.classes
int totalDexNumber = getMultiDexPreferences(context).getInt("dex.number", 1);
List files = new ArrayList(totalDexNumber);
for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++)
{
String fileName = extractedFilePrefix + secondaryNumber + ".zip"; // /data/app/包名-1.apk.classes2.zip ...
File extractedFile = new File(dexDir, fileName); // /data/app/包名-1.apk.classesN.zip
if (extractedFile.isFile())
{
files.add(extractedFile); //如果是文件的话,则添加到List集合中
if (!verifyZipFile(extractedFile)) //验证zip格式是否有误
{
Log.i("MultiDex", "Invalid zip file: " + extractedFile);
throw new IOException("Invalid ZIP file.");
}
}
else
{
throw new IOException("Missing extracted secondary dex file '" +
extractedFile.getPath() + "'");
}
}
return files;
}
performExtractions(sourceApk, dexDir);
private static final String DEX_PREFIX = "classes";
private static final String DEX_SUFFIX = ".dex";
private static final String EXTRACTED_NAME_EXT = ".classes";
private static final String EXTRACTED_SUFFIX = ".zip";
private static List performExtractions(File sourceApk, File dexDir)
throws IOException {
final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; // /data/app/包名.classes
// Ensure that whatever deletions happen in prepareDexDir only happen if the zip that
// contains a secondary dex file in there is not consistent with the latest apk. Otherwise,
// multi-process race conditions can cause a crash loop where one process deletes the zip
// while another had created it.
prepareDexDir(dexDir, extractedFilePrefix); //遍历过滤掉非classes{}.dex的文件
List files = new ArrayList();
final ZipFile apk = new ZipFile(sourceApk); //创建一个zipFile,即安装的apk: // /data/apk/包名-1.apk
try {
int secondaryNumber = 2;
ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); //解压缩zip文件,找到classes2.dex ,classesN.dex....
while (dexFile != null) {
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
File extractedFile = new File(dexDir, fileName); // /data/app/包名-1.apk.classes2.zip
files.add(extractedFile);
Log.i(TAG, "Extraction is needed for file " + extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false; //判断是否解压成功
while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { //最多尝试3次
numAttempts++;
// Create a zip file (extractedFile) containing only the secondary dex file
// (dexFile) from the apk.
extract(apk, dexFile, extractedFile, extractedFilePrefix);
// Verify that the extracted file is indeed a zip file.
isExtractionSuccessful = verifyZipFile(extractedFile); //根据zip格式是否正确判断解压成功
// Log the sha1 of the extracted zip file
Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +
" - length " + extractedFile.getAbsolutePath() + ": " +
extractedFile.length());
if (!isExtractionSuccessful) {
// Delete the extracted file
extractedFile.delete(); //解压失败则删除
if (extractedFile.exists()) {
Log.w(TAG, "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++;
dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
}
} finally {
try {
apk.close();
} catch (IOException e) {
Log.w(TAG, "Failed to close resource", e);
}
}
return files;
}
最后根据当前Android系统的Api版本调用对应的install方法,加载从Dex文件。
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List files)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
InvocationTargetException, NoSuchMethodException, IOException {
if (!files.isEmpty()) {
if (Build.VERSION.SDK_INT >= 19) {
V19.install(loader, files, dexDir); //files就是上面的List集合,// /data/app/包名-1.apk.classes2.zip
} else if (Build.VERSION.SDK_INT >= 14) { // /data/app/包名-1.apk.classesN.zip
V14.install(loader, files, dexDir);
} else {
V4.install(loader, files);
}
}
}
通过上面的分析,我们可以简单的总结一下整个MultiDex的读取过程: