All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.androidx.multidex.MultiDex Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.multidex;

import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;

import dalvik.system.DexFile;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.zip.ZipFile;

/**
 * MultiDex 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. */ public final class MultiDex { static final String TAG = "MultiDex"; private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes"; private static final String CODE_CACHE_NAME = "code_cache"; private static final String CODE_CACHE_SECONDARY_FOLDER_NAME = "secondary-dexes"; private static final int MAX_SUPPORTED_SDK_VERSION = 20; private static final int MIN_SDK_VERSION = 4; private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2; private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1; private static final String NO_KEY_PREFIX = ""; private static final Set installedApk = new HashSet(); private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version")); private MultiDex() {} /** * 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. */ public static void install(Context context) { Log.i(TAG, "Installing application"); 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("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); } try { 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; } 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"); } /** * Patches the instrumentation context class loader by appending extra dex files * loaded from the instrumentation apk and the application apk. This method should be called in * the onCreate of your {@link Instrumentation}, see * {@link com.android.test.runner.MultiDexTestRunner} for an example. * * @param instrumentationContext instrumentation context. * @param targetContext target application context. * @throws RuntimeException if an error occurred preventing the classloader * extension. */ public static void installInstrumentation(Context instrumentationContext, Context targetContext) { Log.i(TAG, "Installing instrumentation"); 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("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); } try { ApplicationInfo instrumentationInfo = getApplicationInfo(instrumentationContext); if (instrumentationInfo == null) { Log.i(TAG, "No ApplicationInfo available for instrumentation, i.e. running on a" + " test Context: MultiDex support library is disabled."); return; } ApplicationInfo applicationInfo = getApplicationInfo(targetContext); if (applicationInfo == null) { Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:" + " MultiDex support library is disabled."); return; } String instrumentationPrefix = instrumentationContext.getPackageName() + "."; File dataDir = new File(applicationInfo.dataDir); doInstallation(targetContext, new File(instrumentationInfo.sourceDir), dataDir, instrumentationPrefix + CODE_CACHE_SECONDARY_FOLDER_NAME, instrumentationPrefix, false); doInstallation(targetContext, new File(applicationInfo.sourceDir), dataDir, CODE_CACHE_SECONDARY_FOLDER_NAME, NO_KEY_PREFIX, false); } catch (Exception e) { Log.e(TAG, "MultiDex installation failure", e); throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ")."); } Log.i(TAG, "Installation done"); } /** * @param mainContext context used to get filesDir, to save preference and to get the * classloader to patch. * @param sourceApk Apk file. * @param dataDir data directory to use for code cache simulation. * @param secondaryFolderName name of the folder for storing extractions. * @param prefsKeyPrefix prefix of all stored preference keys. * @param reinstallOnPatchRecoverableException if set to true, will attempt a clean extraction * if a possibly recoverable exception occurs during classloader patching. */ private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix, boolean reinstallOnPatchRecoverableException) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException { synchronized (installedApk) { if (installedApk.contains(sourceApk)) { return; } installedApk.add(sourceApk); 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 ClassLoader capable of loading DEX * bytecode. We modify its pathList field to append additional DEX file entries. */ ClassLoader loader = getDexClassloader(mainContext); if (loader == null) { return; } try { clearOldDexDir(mainContext); } catch (Throwable t) { Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, " + "continuing without cleaning.", t); } File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName); // MultiDexExtractor is taking the file lock and keeping it until it is closed. // Keep it open during installSecondaryDexes and through forced extraction to ensure no // extraction or optimizing dexopt is running in parallel. MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir); IOException closeException = null; try { List files = extractor.load(mainContext, prefsKeyPrefix, false); try { 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; } } } /** * Returns a {@link Classloader} from the {@link Context} that is capable of reading dex * bytecode or null if the Classloader is not dex-capable e.g: when running on a JVM testing * environment such as Robolectric. */ private static ClassLoader getDexClassloader(Context context) { 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 null; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (loader instanceof dalvik.system.BaseDexClassLoader) { return loader; } } else if (loader instanceof dalvik.system.DexClassLoader || loader instanceof dalvik.system.PathClassLoader) { return loader; } Log.e(TAG, "Context class loader is null or not dex-capable. " + "Must be running in test mode. Skip patching."); return null; } private static ApplicationInfo getApplicationInfo(Context context) { try { /* Due to package install races it is possible for a process to be started from an old * apk even though that apk has been replaced. Querying for ApplicationInfo by package * name may return information for the new apk, leading to a runtime with the old main * dex file and new secondary dex files. This leads to various problems like * ClassNotFoundExceptions. Using context.getApplicationInfo() should result in the * process having a consistent view of the world (even if it is of the old world). The * package install races are eventually resolved and old processes are killed. */ return context.getApplicationInfo(); } 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; } } /** * 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 */ /* package visible for test */ static boolean isVMMultidexCapable(String versionString) { boolean isMultidexCapable = false; if (versionString != null) { StringTokenizer tokenizer = new StringTokenizer(versionString, "."); String majorToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; String minorToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; if (majorToken != null && minorToken != null) { try { int major = Integer.parseInt(majorToken); int minor = Integer.parseInt(minorToken); 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; } private static void installSecondaryDexes(ClassLoader loader, File dexDir, List 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); } } } /** * 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 */ 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()); } /** * 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 */ 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()); } /** * 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); } private static void clearOldDexDir(Context context) throws Exception { 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 File getDexDir(Context context, File dataDir, String secondaryFolderName) throws IOException { File cache = new File(dataDir, CODE_CACHE_NAME); try { mkdirChecked(cache); } catch (IOException e) { /* If we can't emulate code_cache, then store to filesDir. This means abandoning useless * files on disk if the device ever updates to android 5+. But since this seems to * happen only on some devices running android 2, this should cause no pollution. */ cache = new File(context.getFilesDir(), CODE_CACHE_NAME); mkdirChecked(cache); } File dexDir = new File(cache, secondaryFolderName); mkdirChecked(dexDir); return dexDir; } private static void mkdirChecked(File dir) throws IOException { dir.mkdir(); if (!dir.isDirectory()) { File parent = dir.getParentFile(); if (parent == null) { Log.e(TAG, "Failed to create dir " + dir.getPath() + ". Parent file is null."); } else { Log.e(TAG, "Failed to create dir " + dir.getPath() + ". parent file is a dir " + parent.isDirectory() + ", a file " + parent.isFile() + ", exists " + parent.exists() + ", readable " + parent.canRead() + ", writable " + parent.canWrite()); } throw new IOException("Failed to create directory " + dir.getPath()); } } /** * Installer for platform versions 19. */ private static final class V19 { static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException { /* 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. */ Field pathListField = findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList suppressedExceptions = new ArrayList(); expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(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); IOException exception = new IOException("I/O exception during makeDexElement"); exception.initCause(suppressedExceptions.get(0)); throw exception; } } /** * A wrapper around * {@code private static final dalvik.system.DexPathList#makeDexElements}. */ private static Object[] makeDexElements( Object dexPathList, ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class); return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); } } /** * Installer for platform versions 14, 15, 16, 17 and 18. */ private static final class V14 { private interface ElementConstructor { Object newInstance(File file, DexFile dex) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException; } /** * Applies for ICS and early JB (initial release and MR1). */ private static class ICSElementConstructor implements ElementConstructor { private final Constructor elementConstructor; ICSElementConstructor(Class elementClass) throws SecurityException, NoSuchMethodException { elementConstructor = elementClass.getConstructor(File.class, ZipFile.class, DexFile.class); elementConstructor.setAccessible(true); } @Override public Object newInstance(File file, DexFile dex) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException { return elementConstructor.newInstance(file, new ZipFile(file), dex); } } /** * Applies for some intermediate JB (MR1.1). * * See Change-Id: I1a5b5d03572601707e1fb1fd4424c1ae2fd2217d */ private static class JBMR11ElementConstructor implements ElementConstructor { private final Constructor elementConstructor; JBMR11ElementConstructor(Class elementClass) throws SecurityException, NoSuchMethodException { elementConstructor = elementClass .getConstructor(File.class, File.class, DexFile.class); elementConstructor.setAccessible(true); } @Override public Object newInstance(File file, DexFile dex) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { return elementConstructor.newInstance(file, file, dex); } } /** * Applies for latest JB (MR2). * * See Change-Id: Iec4dca2244db9c9c793ac157e258fd61557a7a5d */ private static class JBMR2ElementConstructor implements ElementConstructor { private final Constructor elementConstructor; JBMR2ElementConstructor(Class elementClass) throws SecurityException, NoSuchMethodException { elementConstructor = elementClass .getConstructor(File.class, Boolean.TYPE, File.class, DexFile.class); elementConstructor.setAccessible(true); } @Override public Object newInstance(File file, DexFile dex) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { return elementConstructor.newInstance(file, Boolean.FALSE, file, dex); } } private static final int EXTRACTED_SUFFIX_LENGTH = MultiDexExtractor.EXTRACTED_SUFFIX.length(); private final ElementConstructor elementConstructor; static void install(ClassLoader loader, List additionalClassPathEntries) throws IOException, SecurityException, IllegalArgumentException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException { /* 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. */ Field pathListField = findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); Object[] elements = new V14().makeDexElements(additionalClassPathEntries); try { expandFieldArray(dexPathList, "dexElements", elements); } catch (NoSuchFieldException e) { // dexElements was renamed pathElements for a short period during JB development, // eventually it was renamed back shortly after. Log.w(TAG, "Failed find field 'dexElements' attempting 'pathElements'", e); expandFieldArray(dexPathList, "pathElements", elements); } } private V14() throws ClassNotFoundException, SecurityException, NoSuchMethodException { ElementConstructor constructor; Class elementClass = Class.forName("dalvik.system.DexPathList$Element"); try { constructor = new ICSElementConstructor(elementClass); } catch (NoSuchMethodException e1) { try { constructor = new JBMR11ElementConstructor(elementClass); } catch (NoSuchMethodException e2) { constructor = new JBMR2ElementConstructor(elementClass); } } this.elementConstructor = constructor; } /** * An emulation of {@code private static final dalvik.system.DexPathList#makeDexElements} * accepting only extracted secondary dex files. * OS version is catching IOException and just logging some of them, this version is letting * them through. */ private Object[] makeDexElements(List files) throws IOException, SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Object[] elements = new Object[files.size()]; for (int i = 0; i < elements.length; i++) { File file = files.get(i); elements[i] = elementConstructor.newInstance( file, DexFile.loadDex(file.getPath(), optimizedPathFor(file), 0)); } return elements; } /** * Converts a zip file path of an extracted secondary dex to an output file path for an * associated optimized dex file. */ private static String optimizedPathFor(File path) { // Any reproducible name ending with ".dex" should do but lets keep the same name // as DexPathList.optimizedPathFor File optimizedDirectory = path.getParentFile(); String fileName = path.getName(); String optimizedFileName = fileName.substring(0, fileName.length() - EXTRACTED_SUFFIX_LENGTH) + MultiDexExtractor.DEX_SUFFIX; File result = new File(optimizedDirectory, optimizedFileName); return result.getPath(); } } /** * Installer for platform versions 4 to 13. */ private static final class V4 { static void install(ClassLoader loader, List additionalClassPathEntries) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, IOException { /* The patched class loader is expected to be a descendant of * dalvik.system.DexClassLoader. We modify its * fields mPaths, mFiles, mZips and mDexs to append additional DEX * file entries. */ int extraSize = additionalClassPathEntries.size(); Field pathField = findField(loader, "path"); StringBuilder path = new StringBuilder((String) pathField.get(loader)); String[] extraPaths = new String[extraSize]; File[] extraFiles = new File[extraSize]; ZipFile[] extraZips = new ZipFile[extraSize]; DexFile[] extraDexs = new DexFile[extraSize]; for (ListIterator iterator = additionalClassPathEntries.listIterator(); iterator.hasNext();) { File additionalEntry = iterator.next(); String entryPath = additionalEntry.getAbsolutePath(); path.append(':').append(entryPath); int index = iterator.previousIndex(); extraPaths[index] = entryPath; extraFiles[index] = additionalEntry; extraZips[index] = new ZipFile(additionalEntry); extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0); } pathField.set(loader, path.toString()); expandFieldArray(loader, "mPaths", extraPaths); expandFieldArray(loader, "mFiles", extraFiles); expandFieldArray(loader, "mZips", extraZips); expandFieldArray(loader, "mDexs", extraDexs); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy