简述
MultiDex适用于API版本在4-20
的Android系统 , 即Android 2.1 - 4.4
. 而在这些版本之间 , MultiDex会通过Application.getClassLoader
进行加载. 而如果Dex比较多比较大的话 , 主线程加载Dex时间会很长 , 导致主线程ANR.
由于Android 5.0之后使用ART虚拟机进行dex2oat
, 将多dex在安装的时候将APK中多个Dex进行优化 , 优化过后生成一个ELF文件 , 名为.oat
文件. 在加载后 , 会将oat
文件直接映射到ART虚拟机中使用 , 这样就减少Dex加载的耗时.
MultiDex加载过程简述
在加载过程中 :
- 读取APK的
CRC32
以及modifyTime
进行校验 - 通过反射 , 从
BaseDexClassLoader
中找到pathList
对象 - 通过反射调用
PathList.makeDexElements
创建Elements[]
- 通过反射将
Elements[]
添加到dexElements
数组中 - 后续在该ClassLoader查找类到时候 , 会优先在
dexElements
中开始遍历查找
MultiDex加载过程
- 调用
install
函数进行加载
- 获取ApplicationInfo , 以及data、source目录路径
- 调用
doInstallation
加载Dex
public static void install(Context context) {
...
// 如果当前版本小于4 , 不支持
if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
throw new RuntimeException("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT
+ " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
}
try {
// 获取Application的ApplicationInfo
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:"
+ " MultiDex support library is disabled.");
return;
}
// 传入/data/data路径以及代码路径
doInstallation(context,
new File(applicationInfo.sourceDir),
new File(applicationInfo.dataDir),
CODE_CACHE_SECONDARY_FOLDER_NAME,
NO_KEY_PREFIX,
true);
} catch (Exception e) {
Log.e(TAG, "MultiDex installation failure", e);
throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ").");
}
Log.i(TAG, "install done");
}
doInstallation
- 检查版本
- 从Application中获取
DexClassLoader
- 清理
secondary-dexes
文件夹 - 创建
MultiDexExtractor
用于读取APK中的文件 - 调用
installSecondaryDexes
开始安装classes2.dex
后的Dex文件
private static void doInstallation(Context mainContext, File sourceApk, File dataDir,
String secondaryFolderName, String prefsKeyPrefix,
boolean reinstallOnPatchRecoverableException) {
synchronized (installedApk) {
// 如果已经加载过的APK就直接返回
if (installedApk.contains(sourceApk)) {
return;
}
installedApk.add(sourceApk);
// 如果版本高于20 , 也就5.0以上的版本就会提示
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") + "\"");
}
// 从Application中拿到ClassLoader , 并且进行类型校验
// 判断是否为BaseDexClassLoader还是PathClassLoader
ClassLoader loader = getDexClassloader(mainContext);
if (loader == null) {
return;
}
// 清理secondary-dexes目录
try {
clearOldDexDir(mainContext);
} catch (Throwable t) {
Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
+ "continuing without cleaning.", t);
}
// 得到/data/data/pkg/secondary-dexes目录
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
// 创建MultiDexExtractor , 即MultiDex提取器
MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
IOException closeException = null;
try {
// 通过extractor加载Dex
List<? extends File> files =
extractor.load(mainContext, prefsKeyPrefix, false);
try {
// 安装Dex文件
installSecondaryDexes(loader, dexDir, files);
// Some IOException causes may be fixed by a clean extraction.
} catch (IOException e) {
if (!reinstallOnPatchRecoverableException) {
throw e;
}
Log.w(TAG, "Failed to install extracted secondary dex files, retrying with "
+ "forced extraction", e);
files = extractor.load(mainContext, prefsKeyPrefix, true);
installSecondaryDexes(loader, dexDir, files);
}
} finally {
try {
extractor.close();
} catch (IOException e) {
// Delay throw of close exception to ensure we don't override some exception
// thrown during the try block.
closeException = e;
}
}
if (closeException != null) {
throw closeException;
}
}
}
MultiDexExtractor
- 获取
multidex.lock
文件 , 用于文件锁 - 锁住文件
MultiDexExtractor(File sourceApk, File dexDir) throws IOException {
Log.i(TAG, "MultiDexExtractor(" + sourceApk.getPath() + ", " + dexDir.getPath() + ")");
this.sourceApk = sourceApk;
this.dexDir = dexDir;
// 获取APK的CRC
sourceCrc = getZipCrc(sourceApk);
File lockFile = new File(dexDir, LOCK_FILENAME);
// 拿到Lock文件的文件锁
lockRaf = new RandomAccessFile(lockFile, "rw");
try {
lockChannel = lockRaf.getChannel();
try {
Log.i(TAG, "Blocking on lock " + lockFile.getPath());
// 锁住File Channel
cacheLock = lockChannel.lock();
} catch (IOException | RuntimeException | Error e) {
closeQuietly(lockChannel);
throw e;
}
Log.i(TAG, lockFile.getPath() + " locked");
} catch (IOException | RuntimeException | Error e) {
closeQuietly(lockRaf);
throw e;
}
}
load
- 检查文件锁是否失效
- 检查APK的CRC、ModifyTime是否与之前的APK CRC、ModifyTime一样
- 如果不一样 , 则调用
performExtractions
来清理之前的无效文件以及读取Dex - 将最新的Dex的CRC与ModifyTime保存到SP中
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<ExtractedDex> files;
// forceReload为false , 判断文件的modify时间以及CRC是否相同
if (!forceReload && !isModified(context, sourceApk, sourceCrc, prefsKeyPrefix)) {
// 本次文件的CRC与modify time与之前的Dex文件相同的话 , 就会来加载
try {
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.");
}
// 如果CRC与之前SP中保存不一致的话 , 就会通过这个方法查找dex文件个数
files = performExtractions();
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc,
files);
}
Log.i(TAG, "load found " + files.size() + " secondary dex files");
return files;
}
performExtractions
- 清理
secondary-classes
文件夹 - 从
classes2.dex
开始从APK中读取Dex文件 - 将DexFile写入到本地临时文件中
- 计算文件CRC
- 将所有Dex的CRC保存到List中返回
private List<ExtractedDex> performExtractions() throws IOException {
// 得到xxx.apk.classes
final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
// 清理secondary-classes文件夹
clearDexDir();
List<ExtractedDex> files = new ArrayList<ExtractedDex>();
// 创建ZipFile
final ZipFile apk = new ZipFile(sourceApk);
try {
int secondaryNumber = 2;
// 得到classes2.dex文件, 因为classes.dex已经加载
ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
while (dexFile != null) {
// 找到dex的文件名
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
// 创建DexFile路径文件
ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName);
files.add(extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
// 每个Dex尝试最多尝试读取3次
while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
numAttempts++;
// 创建Dex临时文件 , 也就是把Zip里的文件读取后 , 写入一个临时文件
extract(apk, dexFile, extractedFile, extractedFilePrefix);
// 计算临时文件的CRC32
try {
extractedFile.crc = getZipCrc(extractedFile);
// 计算成功
isExtractionSuccessful = true;
} catch (IOException e) {
isExtractionSuccessful = false;
Log.w(TAG, "Failed to read crc from " + extractedFile.getAbsolutePath(), e);
}
if (!isExtractionSuccessful) {
// 如果提取失败 , 删除临时文件
extractedFile.delete();
if (extractedFile.exists()) {
Log.w(TAG, "Failed to delete corrupted secondary dex '" +
extractedFile.getPath() + "'");
}
}
}
// 如果提取3次失败 , 抛异常
if (!isExtractionSuccessful) {
throw new IOException("Could not create zip file " +
extractedFile.getAbsolutePath() + " for secondary dex (" +
secondaryNumber + ")");
}
// 开始读取下一个classes3.dex文件
secondaryNumber++;
// 获取下一个dexFile
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;
}
putStoredApkInfo
- 将CRC更新到SharedPreferences中
- 使用
commit
写入
private static void putStoredApkInfo(Context context, String keyPrefix, long timeStamp,
long crc, List<ExtractedDex> extractedDexes) {
SharedPreferences prefs = getMultiDexPreferences(context);
SharedPreferences.Editor edit = prefs.edit();
edit.putLong(keyPrefix + KEY_TIME_STAMP, timeStamp);
// 将APK文件的CRC保存到SP中
edit.putLong(keyPrefix + KEY_CRC, crc);
edit.putInt(keyPrefix + KEY_DEX_NUMBER, extractedDexes.size() + 1);
int extractedDexId = 2;
// 保存从classes2.dex后的CRC以及时间
for (ExtractedDex dex : extractedDexes) {
edit.putLong(keyPrefix + KEY_DEX_CRC + extractedDexId, dex.crc);
edit.putLong(keyPrefix + KEY_DEX_TIME + extractedDexId, dex.lastModified());
extractedDexId++;
}
// 需要一个同步写入的方法
edit.commit();
}
7.installSecondaryDexes
开始加载Dex
- 根据当前Android版本进行安装
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);
}
}
}
install
- V19版本中, 通过pathList对象到makeDexList创建Elements
- 合并到主Dex中的Elements中
static void install(ClassLoader loader,
List<? extends File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException,
IOException {
// 从PathClassLaoder中读取pathList属性
Field pathListField = findField(loader, "pathList");
// 获取pathList对象
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// 调用makeDexElements , 并且添加到dexPathList对象中
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
...
}
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
// 调用pathList.makeDexElements生成Elements对象
Method makeDexElements =
findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
ArrayList.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
suppressedExceptions);
}
网友评论