Multidex拆分包原理解析

一、Android虚拟机加载class原理

我们知道Java在运行时加载对应的类是通过ClassLoader来实现的,ClassLoader本身是一个抽象来,Android中使用PathClassLoader类作为Android的默认的类加载器,
PathClassLoader其实实现的就是简单的从文件系统中加载类文件。PathClassLoade本身继承自BaseDexClasoader,BaseDexClassLoader重写了findClass方法。
详细的可以看这篇文章:Android热更新实现原理,本篇文章不做介绍,这里只从源码上讲解multidex的实现原理。

二、源码解析
  1. 首先从Multidex.install方法开始分析,以下是install的核心代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public static void install(Context context) {
Log.i(TAG, "install");
if (IS_VM_MULTIDEX_CAPABLE) {
Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
return;
}

if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT
+ " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
}

try {
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
// Looks like running on a test Context, so just return without patching.
return;
}

synchronized (installedApk) {
String apkPath = applicationInfo.sourceDir;
if (installedApk.contains(apkPath)) {
return;
}
installedApk.add(apkPath);

if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
......
}

ClassLoader loader;
try {
//这里获取到BaseDexClassLoader的类加载器。
loader = context.getClassLoader();
} catch (RuntimeException e) {
return;
}
if (loader == null) {
return;
}

try {
clearOldDexDir(context);
} catch (Throwable t) {

}

File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
if (checkValidZipFiles(files)) {
//这里加载第二个dex文件
installSecondaryDexes(loader, dexDir, files);
} else {
// Try again, but this time force a reload of the zip file.
files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
if (checkValidZipFiles(files)) {
installSecondaryDexes(loader, dexDir, files);
} else {
// Second time didn't work, give up
throw new RuntimeException("Zip files were not valid.");
}
}
}

} catch (Exception e) {
Log.e(TAG, "Multidex installation failure", e);
throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");
}
Log.i(TAG, "install done");
}

我们先分析下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version"));
private static final int MIN_SDK_VERSION = 4; //4是android 1.6

public static void install(Context context) {
Log.i(TAG, "install");
//isVMMultidexCapable()方法,判断虚拟机是否支持multidex
if (IS_VM_MULTIDEX_CAPABLE) {
Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
return;
}
//在sdk1.6以前是不支持multidex的
if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT
+ " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
}
.......
}

我们看下isVMMultidexCapable方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//这里的versionString是 java.vm.version
static boolean isVMMultidexCapable(String versionString) {
boolean isMultidexCapable = false;
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
}
}
}
Log.i(TAG, "VM with version " + versionString + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
return isMultidexCapable;
}

getProperty()的参数:

mean name example
java.vm.version VM implementation version 1.2.0

这里主要判断虚拟机是否已经支持multidex,如不支持就直接返回。

继续分析,经过一系列的判断,验证等步骤,最终会执行installSecondaryDexes方法,这个方法才是加载我们拆分的dex文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<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);
}
}
}

首先判断,当前的sdk版本,有3个不同的方法,分别是V19 V14 V4,我们分析下V19.install方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
//这里通过反射获取BaseClassLoader中的变量名为”pathList“ Field
Field pathListField = findField(loader, "pathList");
//这里拿到pathList这个对象
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
//关键步骤,反射调用makeDexElements函数,
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(loader, "dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions = (IOException[]) suppressedExceptionsField.get(loader);
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(loader, dexElementsSuppressedExceptions);
}
}

这里的pathDexList是BaseClassLoader下面的一个成员变量,它的类型是DexPathList,这个类主要负责加载Dex并重组;接着看下expandFieldArray的源码,这个函数主要的目的是,先通过反射拿到名为dexElements的field,这个dexElements就是我们刚刚说的Elements数组,然后将刚刚makeDexElements生成的Elements数组放在dexElements的首部,makeDexElements生成的数组就是加载Dex文件的数组,这样就完成了将dex的class放在ClassLoader前面的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
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);
}

最后分析下makeDexElements这个函数,首先调用的是V19下面的makeDexElements函数:

1
2
3
4
5
6
7
8
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makeDexElements}.
*/
private static Object[] makeDexElements(bject 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);
}

事实上,也是通过反射调用DexPathList里面的makeDexElements方法,具体就不做分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();

for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();

if (file.isDirectory()) {
elements.add(new Element(file, true, null, null));
} else if (file.isFile()){
if (name.endsWith(DEX_SUFFIX)) {
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else {
zip = file;

try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}

if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}

return elements.toArray(new Element[elements.size()]);
}

王洋 wechat
我的微信号,欢迎交流~

热评文章