只介绍主要流程,multiDex源码已经上传到https://github.com/AlexSmille/google-android-support-source-analyze,包含了
BaseDexClassloader
等一些android源码。
在5.0
以上,会自动加载一个apk中所有的classesX.dex
,但是低于5.0
时需要手动导入。
具体判断是否需要手动导入如下:
static boolean isVMMultidexCapable(String versionString) {
//You can verify which runtime is in use by calling System.getProperty("java.vm.version").
// If ART is in use, the property's value is "2.0.0" or higher.
boolean isMultidexCapable = false;
//2.1.0的时候开始支持默认加载外部classes.dex,根据vm版本号判断
if (versionString != null) {
Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
if (matcher.matches()) {
try {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
|| ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
&& (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
} catch (NumberFormatException e) {
// let isMultidexCapable be false
}
}
}
return isMultidexCapable;
}
如果已经支持自动导入,则结束流程,否则进入下一步。
doInstallation()
方法调用doInstallation()
传入四个参数
Context mainContext
:上下文对象。File sourceApk
:安装额apk的存放路径。/data/app/….File dataDir
: apk的数据存放目录。/data/data/package/
String secondaryFolderName
:存放缓存的目录文件名secondary-dexes
String prefsKeyPrefix
:boolean reinstallOnPatchRecoverableException
:如果导入失败,是重试还是直接抛出异常。doInstallation()
方法判断一些必要条件
if (installedApk.contains(sourceApk)) {
// 是否包含要导入的apk,防止重复导入
return;
}
// 将apk添加到集合中
installedApk.add(sourceApk);
// 5.0以上不用导入 IS_VM_MULTIDEX_CAPABLE
if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
Log.w(TAG, "MultiDex is not guaranteed to work in SDK version "
+ Build.VERSION.SDK_INT + ": SDK version higher than "
+ MAX_SUPPORTED_SDK_VERSION + " 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") + "\"");
}
}
classesX.dex
的目录/data/data/package/code_cache/secondary-dexes
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
创建dexDir
,即存放dex
的目录。getDexDir()
代码如下:
private static File getDexDir(Context context, File dataDir, String secondaryFolderName)
throws IOException {
// data/data/package/code_cache
File cache = new File(dataDir, CODE_CACHE_NAME);
try {
mkdirChecked(cache);
} catch (IOException e) {
/* If we can't emulate code_cache, then store to filesDir. This means abandoning useless
* files on disk if the device ever updates to android 5+. But since this seems to
* happen only on some devices running android 2, this should cause no pollution.
*/
cache = new File(context.getFilesDir(), CODE_CACHE_NAME);
mkdirChecked(cache);
}
// data/data/package/code_cache/secondary-dexes
File dexDir = new File(cache, secondaryFolderName);
mkdirChecked(dexDir);
return dexDir;
}
MultiDexExtractor
对象,准备提取classesX.dex
// MultiDexExtractor is taking the file lock and keeping it until it is closed.
// Keep it open during installSecondaryDexes and through forced extraction to ensure no
// extraction or optimizing dexopt is running in parallel.
MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
MultiDexExtractor
提取并存储到data/data/package/code_cache/secondary-dexes/
目录下在此步骤中,会将apk
中的classesX.dex
提取并存放为data/data/package/code_cache/secondary-dexes/package--1.apk.classN.zip
,以便后续加载到内存中。
关键方法MultiDexExtractor.load()
方法
List extends File> load(Context context, String prefsKeyPrefix, boolean forceReload)
throws IOException {
Log.i(TAG, "MultiDexExtractor.load(" + sourceApk.getPath() + ", " + forceReload + ", " +
prefsKeyPrefix + ")");
if (!cacheLock.isValid()) {
throw new IllegalStateException("MultiDexExtractor was closed");
}
List files;
if (!forceReload && !isModified(context, sourceApk, sourceCrc, prefsKeyPrefix)) {
// apk信息没有发生变化
try {
// 加载已存在的classX.zip
files = loadExistingExtractions(context, prefsKeyPrefix);
} catch (IOException ioe) {
Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
+ " falling back to fresh extraction", ioe);
files = performExtractions();
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc,
files);
}
} else {
if (forceReload) {
Log.i(TAG, "Forced extraction must be performed.");
} else {
Log.i(TAG, "Detected that extraction must be performed.");
}
// 重新从apk中提取classesX.dex
files = performExtractions();
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc,
files);
}
Log.i(TAG, "load found " + files.size() + " secondary dex files");
return files;
}
根据apk是否有新变化,判断是直接读取之前获取的classX.zip
文件,还是从apk
中重新提取classes.dex
。
看一下提取的方法
private List performExtractions() throws IOException {
final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
// It is safe to fully clear the dex dir because we own the file lock so no other process is
// extracting or running optimizing dexopt. It may cause crash of already running
// applications if for whatever reason we end up extracting again over a valid extraction.
// 清楚缓存目录
clearDexDir();
List files = new ArrayList();
// 获取apk的zip对象
final ZipFile apk = new ZipFile(sourceApk);
int secondaryNumber = 2;
// 从apk中获取对应classesX.dex
ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
while (dexFile != null) {
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName);
files.add(extractedFile);
Log.i(TAG, "Extraction is needed for file " + extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
numAttempts++;
// Create a zip file (extractedFile) containing only the secondary dex file
// (dexFile) from the apk.
// 创建zip文件,并提取.dex文件
extract(apk, dexFile, extractedFile, extractedFilePrefix);
// .... 后面的都是一些判断
}
}
return files;
}
此段对应的log
如下
// MultidexExtractor.load()
07-04 16:19:52.296 I/MultiDex( 4387): MultiDexExtractor.load(/data/app/com.spearbothy.android-2.apk, false, )
// 有新信息变化,删除老的缓存文件
07-04 16:19:52.306 I/MultiDex( 4387): Detected that extraction must be performed.
07-04 16:19:52.306 I/MultiDex( 4387): Trying to delete old file /data/data/com.spearbothy.android/code_cache/secondary-dexes/com.spearbothy.android-1.apk.classes2.zip of size 185848
07-04 16:19:52.306 I/MultiDex( 4387): Deleted old file /data/data/com.spearbothy.android/code_cache/secondary-dexes/com.spearbothy.android-1.apk.classes2.zip
07-04 16:19:52.306 I/MultiDex( 4387): Trying to delete old file /data/data/com.spearbothy.android/code_cache/secondary-dexes/com.spearbothy.android-1.apk.classes2.dex of size 675216
07-04 16:19:52.306 I/MultiDex( 4387): Deleted old file /data/data/com.spearbothy.android/code_cache/secondary-dexes/com.spearbothy.android-1.apk.classes2.dex
// 提取apk中的class文件
07-04 16:19:52.386 I/MultiDex( 4387): Extraction is needed for file /data/data/com.spearbothy.android/code_cache/secondary-dexes/com.spearbothy.android-2.apk.classes2.zip
07-04 16:19:52.386 I/MultiDex( 4387): Extracting /data/data/com.spearbothy.android/code_cache/secondary-dexes/tmp-com.spearbothy.android-2.apk.classes762107569.zip
07-04 16:19:56.016 I/MultiDex( 4387): Renaming to /data/data/com.spearbothy.android/code_cache/secondary-dexes/com.spearbothy.android-2.apk.classes2.zip
07-04 16:19:56.016 I/MultiDex( 4387): Extraction succeeded '/data/data/com.spearbothy.android/code_cache/secondary-dexes/com.spearbothy.android-2.apk.classes2.zip': length 3843460 - crc: 28802720
BaseDexClassLoader
中在第6步的时候,将.dex
文件提取出来保存到了对应的目录,那么只需要加载就可以了。
// 获取提出来的`dex`文件目录
files = extractor.load(mainContext, prefsKeyPrefix, true);
// 导入
installSecondaryDexes(loader, dexDir, files);
private static void installSecondaryDexes(ClassLoader loader, File dexDir,
List extends File> files)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
InvocationTargetException, NoSuchMethodException, IOException, SecurityException,
ClassNotFoundException, InstantiationException {
if (!files.isEmpty()) {
if (Build.VERSION.SDK_INT >= 19) {
V19.install(loader, files, dexDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(loader, files);
} else {
V4.install(loader, files);
}
}
}
不同版本,加载的方式不同,所以根据sdk
版本选择不同的加载方式。因为5.0
以上不需要加载,所以只需要到19
即可。
虽然加载的方式不同,但核心的就是将每一个.zip
转化为DexPathList.Element
,添加到DexPathList
的elements
字段中。