MultiDexpublic final class MultiDex extends Object Monkey patches {@link Context#getClassLoader() the application context class
loader} in order to load classes from more than one dex file. The primary
{@code classes.dex} must contain the classes necessary for calling this
class methods. Secondary dex files named classes2.dex, classes3.dex... found
in the application apk will be added to the classloader after first call to
{@link #install(Context)}.
This library provides compatibility for platforms with API level 4 through 20. This library does
nothing on newer versions of the platform which provide built-in support for secondary dex files. |
Fields Summary |
---|
static final String | TAG | private static final String | OLD_SECONDARY_FOLDER_NAME | private static final String | SECONDARY_FOLDER_NAME | private static final int | MAX_SUPPORTED_SDK_VERSION | private static final int | MIN_SDK_VERSION | private static final int | VM_WITH_MULTIDEX_VERSION_MAJOR | private static final int | VM_WITH_MULTIDEX_VERSION_MINOR | private static final Set | installedApk | private static final boolean | IS_VM_MULTIDEX_CAPABLE |
Constructors Summary |
---|
private MultiDex()
|
Methods Summary |
---|
private static boolean | checkValidZipFiles(java.util.List files)Returns whether all files in the list are valid zip files. If {@code files} is empty, then
returns true.
for (File file : files) {
if (!MultiDexExtractor.verifyZipFile(file)) {
return false;
}
}
return true;
| private static void | clearOldDexDir(android.content.Context context)
File dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME);
if (dexDir.isDirectory()) {
Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ").");
File[] files = dexDir.listFiles();
if (files == null) {
Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
return;
}
for (File oldFile : files) {
Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size "
+ oldFile.length());
if (!oldFile.delete()) {
Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
} else {
Log.i(TAG, "Deleted old file " + oldFile.getPath());
}
}
if (!dexDir.delete()) {
Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath());
} else {
Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath());
}
}
| private static void | expandFieldArray(java.lang.Object instance, java.lang.String fieldName, java.lang.Object[] extraElements)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.
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);
| private static java.lang.reflect.Field | findField(java.lang.Object instance, java.lang.String name)Locates a given field anywhere in the class inheritance hierarchy.
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 java.lang.reflect.Method | findMethod(java.lang.Object instance, java.lang.String name, java.lang.Class parameterTypes)Locates a given method anywhere in the class inheritance hierarchy.
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 android.content.pm.ApplicationInfo | getApplicationInfo(android.content.Context context)
PackageManager pm;
String packageName;
try {
pm = context.getPackageManager();
packageName = context.getPackageName();
} catch (RuntimeException e) {
/* Ignore those exceptions so that we don't break tests relying on Context like
* a android.test.mock.MockContext or a android.content.ContextWrapper with a null
* base Context.
*/
Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " +
"Must be running in test mode. Skip patching.", e);
return null;
}
if (pm == null || packageName == null) {
// This is most likely a mock context, so just return without patching.
return null;
}
ApplicationInfo applicationInfo =
pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
return applicationInfo;
| public static void | install(android.content.Context context)Patches the application context class loader by appending extra dex files
loaded from the application apk. This method should be called in the
attachBaseContext of your {@link Application}, see
{@link MultiDexApplication} for more explanation and an example.
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) {
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") + "\"");
}
/* 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.
*/
ClassLoader loader;
try {
loader = context.getClassLoader();
} catch (RuntimeException e) {
/* Ignore those exceptions so that we don't break tests relying on Context like
* a android.test.mock.MockContext or a android.content.ContextWrapper with a
* null base Context.
*/
Log.w(TAG, "Failure while trying to obtain Context class loader. " +
"Must be running in test mode. Skip patching.", e);
return;
}
if (loader == null) {
// Note, the context class loader is null when running Robolectric tests.
Log.e(TAG,
"Context class loader is null. Must be running in test mode. "
+ "Skip patching.");
return;
}
try {
clearOldDexDir(context);
} catch (Throwable t) {
Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
+ "continuing without cleaning.", t);
}
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
if (checkValidZipFiles(files)) {
installSecondaryDexes(loader, dexDir, files);
} else {
Log.w(TAG, "Files were not valid zip files. Forcing a reload.");
// 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");
| private static void | installSecondaryDexes(java.lang.ClassLoader loader, java.io.File dexDir, java.util.List files)
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);
}
}
| static boolean | isVMMultidexCapable(java.lang.String versionString)Identifies if the current VM has a native support for multidex, meaning there is no need for
additional installation by this library.
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;
|
|