FileDocCategorySizeDatePackage
MultiDex.javaAPI DocAndroid 5.1 API22084Thu Mar 12 22:22:48 GMT 2015android.support.multidex

MultiDex

public 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 booleancheckValidZipFiles(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 voidclearOldDexDir(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 voidexpandFieldArray(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.

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.

        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.FieldfindField(java.lang.Object instance, java.lang.String name)
Locates a given field anywhere in the class inheritance hierarchy.

param
instance an object to search the field into.
param
name field name
return
a field object
throws
NoSuchFieldException if the field cannot be located

        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.MethodfindMethod(java.lang.Object instance, java.lang.String name, java.lang.Class parameterTypes)
Locates a given method anywhere in the class inheritance hierarchy.

param
instance an object to search the method into.
param
name method name
param
parameterTypes method parameter types
return
a method object
throws
NoSuchMethodException if the method cannot be located

        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.ApplicationInfogetApplicationInfo(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 voidinstall(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.

param
context application context.
throws
RuntimeException if an error occurred preventing the classloader extension.

        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 voidinstallSecondaryDexes(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 booleanisVMMultidexCapable(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.

return
true if the VM handles multidex

        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;