MultiDex分析
2018-01-09 本文已影响90人
act262
MultiDex support 包是为了解决SDK 20以前单个dex文件的方法数量限制问题(65535 = 64k方法数问题)
MultiDex#install() -> doInstallation()
dex 保存位置 /data/data/pkg/code_cache/secondary-dexes
1. 从安装位置的apk包(/data/app/pkg-1.apk)中提取classesN.dex文件到数据区(/data/data/pkg/code_cache/secondary-dexes/xxx.zip)
private static void doInstallation(Context mainContext, File sourceApk, File dataDir,
String secondaryFolderName, String prefsKeyPrefix) throws IOException,
IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
InvocationTargetException, NoSuchMethodException {
// ...
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/code_cache/secondary-dexes/
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
// 加载dex文件
List<? extends File> files =
MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false);
// 注入second dex
installSecondaryDexes(loader, dexDir, files);
}
}
// 1. 首次、重新提取dex 文件
MultiDexExtractor#load() -> performExtractions()
将apk包中classes2.dex、classes3.dex...等提取出来,放到/data/data/pkg/code_cache/secondary-dexes/目录下,然后返回这些List<File>
提取出来的dex文件保存了其对应的信息,如果文件被修改,那么会触发重新提取的操作
// 2. 直接执行已提取过的dex zip文件
MultiDexExtractor#load() -> loadExistingExtractions()
2. 注入second dex
private static void installSecondaryDexes(ClassLoader loader, File dexDir,
List<? extends File> files)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
InvocationTargetException, NoSuchMethodException, IOException {
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, dexDir);
} else {
V4.install(loader, files);
}
}
}
// 反射获取指定对象的字段对象,用于后面修改对象值
private static Field findField(Object instance, String name) throws NoSuchFieldException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
}
// 反射获取指定对象的方法对象,用于后面调用改方法
private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method " + name + " with parameters " +
Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
}
// 将指定对象的字段值加上新的内容值
private static void expandFieldArray(Object instance, String fieldName,
Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(
original.getClass().getComponentType(), original.length + extraElements.length);
System.arraycopy(original, 0, combined, 0, original.length);
System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
jlrField.set(instance, combined);
}
v19
/**
* Installer for platform versions 19.
*/
private static final class V19 {
private static void install(ClassLoader loader,
List<? extends File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// 在DexPathList#dexElements[] 附加上SecondDex内容
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makeDexElement", e);
}
Field suppressedExceptionsField =
findField(dexPathList, "dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions =
(IOException[]) suppressedExceptionsField.get(dexPathList);
if (dexElementsSuppressedExceptions == null) {
dexElementsSuppressedExceptions =
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(dexPathList, dexElementsSuppressedExceptions);
}
}
// 调用DexPathList#makeDexElements()生成 `Element[]`
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method makeDexElements =
findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
ArrayList.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
suppressedExceptions);
}
}
这里的操作是把额外的dex文件附加到classloader中去
相似的做法是用来做热修复的方式,把修复的dex插入到前面去
http://androidxref.com/8.0.0_r4/search?q=BaseDexClassLoader&project=libcore