blob: 142f125abe39d80c18b6c073c03ad45a5ad586df [file] [log] [blame]
Maurice Chu667f9a82013-10-16 13:12:22 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.multidex;
18
Yohann Rousseldd3cc222014-04-22 10:47:53 +020019import android.app.Application;
Maurice Chu667f9a82013-10-16 13:12:22 -070020import android.content.Context;
21import android.content.pm.ApplicationInfo;
Maurice Chu667f9a82013-10-16 13:12:22 -070022import android.os.Build;
23import android.util.Log;
24
Yohann Roussel602c6ca2014-03-28 17:35:02 +010025import dalvik.system.DexFile;
26
Maurice Chu667f9a82013-10-16 13:12:22 -070027import java.io.File;
28import java.io.IOException;
29import java.lang.reflect.Array;
30import java.lang.reflect.Field;
31import java.lang.reflect.InvocationTargetException;
32import java.lang.reflect.Method;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.HashSet;
36import java.util.List;
37import java.util.ListIterator;
38import java.util.Set;
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020039import java.util.regex.Matcher;
40import java.util.regex.Pattern;
Maurice Chu66f379f2013-11-14 19:08:32 -080041import java.util.zip.ZipFile;
Maurice Chu667f9a82013-10-16 13:12:22 -070042
43/**
Sebastien Hertz8be7c7a2016-07-12 10:03:13 +020044 * MultiDex patches {@link Context#getClassLoader() the application context class
Maurice Chu667f9a82013-10-16 13:12:22 -070045 * loader} in order to load classes from more than one dex file. The primary
Yohann Rousseldd3cc222014-04-22 10:47:53 +020046 * {@code classes.dex} must contain the classes necessary for calling this
47 * class methods. Secondary dex files named classes2.dex, classes3.dex... found
48 * in the application apk will be added to the classloader after first call to
Maurice Chu667f9a82013-10-16 13:12:22 -070049 * {@link #install(Context)}.
50 *
51 * <p/>
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020052 * This library provides compatibility for platforms with API level 4 through 20. This library does
53 * nothing on newer versions of the platform which provide built-in support for secondary dex files.
Maurice Chu667f9a82013-10-16 13:12:22 -070054 */
55public final class MultiDex {
56
57 static final String TAG = "MultiDex";
58
Yohann Roussel590a07e2014-07-21 17:47:26 +020059 private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes";
60
Yohann Roussel606af942015-05-12 17:40:52 +020061 private static final String CODE_CACHE_NAME = "code_cache";
62
63 private static final String CODE_CACHE_SECONDARY_FOLDER_NAME = "secondary-dexes";
Maurice Chu667f9a82013-10-16 13:12:22 -070064
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020065 private static final int MAX_SUPPORTED_SDK_VERSION = 20;
Maurice Chu667f9a82013-10-16 13:12:22 -070066
67 private static final int MIN_SDK_VERSION = 4;
68
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020069 private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
70
71 private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
72
Maurice Chu667f9a82013-10-16 13:12:22 -070073 private static final Set<String> installedApk = new HashSet<String>();
74
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020075 private static final boolean IS_VM_MULTIDEX_CAPABLE =
76 isVMMultidexCapable(System.getProperty("java.vm.version"));
77
Maurice Chu667f9a82013-10-16 13:12:22 -070078 private MultiDex() {}
79
80 /**
81 * Patches the application context class loader by appending extra dex files
Yohann Rousseldd3cc222014-04-22 10:47:53 +020082 * loaded from the application apk. This method should be called in the
83 * attachBaseContext of your {@link Application}, see
84 * {@link MultiDexApplication} for more explanation and an example.
Maurice Chu667f9a82013-10-16 13:12:22 -070085 *
86 * @param context application context.
87 * @throws RuntimeException if an error occurred preventing the classloader
88 * extension.
89 */
90 public static void install(Context context) {
Yohann Roussel602c6ca2014-03-28 17:35:02 +010091 Log.i(TAG, "install");
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020092 if (IS_VM_MULTIDEX_CAPABLE) {
93 Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
94 return;
95 }
Maurice Chu667f9a82013-10-16 13:12:22 -070096
97 if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
98 throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT
99 + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
100 }
101
Maurice Chu667f9a82013-10-16 13:12:22 -0700102 try {
Yohann Rousseld79604b2014-07-08 16:50:10 +0200103 ApplicationInfo applicationInfo = getApplicationInfo(context);
Maurice Chu667f9a82013-10-16 13:12:22 -0700104 if (applicationInfo == null) {
Yohann Rousseld79604b2014-07-08 16:50:10 +0200105 // Looks like running on a test Context, so just return without patching.
Maurice Chu667f9a82013-10-16 13:12:22 -0700106 return;
107 }
108
109 synchronized (installedApk) {
110 String apkPath = applicationInfo.sourceDir;
111 if (installedApk.contains(apkPath)) {
112 return;
113 }
114 installedApk.add(apkPath);
115
Yohann Roussel7b86f7e2014-04-22 11:12:54 +0200116 if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
117 Log.w(TAG, "MultiDex is not guaranteed to work in SDK version "
118 + Build.VERSION.SDK_INT + ": SDK version higher than "
119 + MAX_SUPPORTED_SDK_VERSION + " should be backed by "
120 + "runtime with built-in multidex capabilty but it's not the "
121 + "case here: java.vm.version=\""
122 + System.getProperty("java.vm.version") + "\"");
Maurice Chu667f9a82013-10-16 13:12:22 -0700123 }
124
125 /* The patched class loader is expected to be a descendant of
126 * dalvik.system.BaseDexClassLoader. We modify its
127 * dalvik.system.DexPathList pathList field to append additional DEX
128 * file entries.
129 */
130 ClassLoader loader;
131 try {
132 loader = context.getClassLoader();
133 } catch (RuntimeException e) {
134 /* Ignore those exceptions so that we don't break tests relying on Context like
135 * a android.test.mock.MockContext or a android.content.ContextWrapper with a
136 * null base Context.
137 */
138 Log.w(TAG, "Failure while trying to obtain Context class loader. " +
139 "Must be running in test mode. Skip patching.", e);
140 return;
141 }
142 if (loader == null) {
143 // Note, the context class loader is null when running Robolectric tests.
144 Log.e(TAG,
145 "Context class loader is null. Must be running in test mode. "
146 + "Skip patching.");
147 return;
148 }
149
Yohann Roussel590a07e2014-07-21 17:47:26 +0200150 try {
151 clearOldDexDir(context);
152 } catch (Throwable t) {
153 Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
154 + "continuing without cleaning.", t);
155 }
156
Yohann Roussel606af942015-05-12 17:40:52 +0200157 File dexDir = getDexDir(context, applicationInfo);
Maurice Chu7e267a32014-01-15 19:02:18 -0800158 List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
Maurice Chucc63eda2013-12-02 15:39:59 -0800159 if (checkValidZipFiles(files)) {
160 installSecondaryDexes(loader, dexDir, files);
161 } else {
162 Log.w(TAG, "Files were not valid zip files. Forcing a reload.");
163 // Try again, but this time force a reload of the zip file.
Maurice Chu7e267a32014-01-15 19:02:18 -0800164 files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
Yohann Roussel602c6ca2014-03-28 17:35:02 +0100165
Maurice Chucc63eda2013-12-02 15:39:59 -0800166 if (checkValidZipFiles(files)) {
167 installSecondaryDexes(loader, dexDir, files);
Maurice Chu667f9a82013-10-16 13:12:22 -0700168 } else {
Maurice Chucc63eda2013-12-02 15:39:59 -0800169 // Second time didn't work, give up
170 throw new RuntimeException("Zip files were not valid.");
Maurice Chu667f9a82013-10-16 13:12:22 -0700171 }
172 }
173 }
174
175 } catch (Exception e) {
176 Log.e(TAG, "Multidex installation failure", e);
177 throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");
178 }
Yohann Roussel602c6ca2014-03-28 17:35:02 +0100179 Log.i(TAG, "install done");
Maurice Chu667f9a82013-10-16 13:12:22 -0700180 }
181
Jon Noack87738872017-01-12 12:16:58 -0600182 private static ApplicationInfo getApplicationInfo(Context context) {
Yohann Rousseld79604b2014-07-08 16:50:10 +0200183 try {
Jon Noack87738872017-01-12 12:16:58 -0600184 /* Due to package install races it is possible for a process to be started from an old
185 * apk even though that apk has been replaced. Querying for ApplicationInfo by package
186 * name may return information for the new apk, leading to a runtime with the old main
187 * dex file and new secondary dex files. This leads to various problems like
188 * ClassNotFoundExceptions. Using context.getApplicationInfo() should result in the
189 * process having a consistent view of the world (even if it is of the old world). The
190 * package install races are eventually resolved and old processes are killed.
191 */
192 return context.getApplicationInfo();
Yohann Rousseld79604b2014-07-08 16:50:10 +0200193 } catch (RuntimeException e) {
194 /* Ignore those exceptions so that we don't break tests relying on Context like
195 * a android.test.mock.MockContext or a android.content.ContextWrapper with a null
196 * base Context.
197 */
198 Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " +
199 "Must be running in test mode. Skip patching.", e);
200 return null;
201 }
Yohann Rousseld79604b2014-07-08 16:50:10 +0200202 }
203
Yohann Roussel7b86f7e2014-04-22 11:12:54 +0200204 /**
205 * Identifies if the current VM has a native support for multidex, meaning there is no need for
206 * additional installation by this library.
207 * @return true if the VM handles multidex
208 */
209 /* package visible for test */
210 static boolean isVMMultidexCapable(String versionString) {
211 boolean isMultidexCapable = false;
212 if (versionString != null) {
213 Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
214 if (matcher.matches()) {
215 try {
216 int major = Integer.parseInt(matcher.group(1));
217 int minor = Integer.parseInt(matcher.group(2));
218 isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
219 || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
220 && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
221 } catch (NumberFormatException e) {
222 // let isMultidexCapable be false
223 }
224 }
225 }
226 Log.i(TAG, "VM with version " + versionString +
227 (isMultidexCapable ?
228 " has multidex support" :
229 " does not have multidex support"));
230 return isMultidexCapable;
231 }
232
Maurice Chucc63eda2013-12-02 15:39:59 -0800233 private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)
234 throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
235 InvocationTargetException, NoSuchMethodException, IOException {
236 if (!files.isEmpty()) {
237 if (Build.VERSION.SDK_INT >= 19) {
238 V19.install(loader, files, dexDir);
239 } else if (Build.VERSION.SDK_INT >= 14) {
240 V14.install(loader, files, dexDir);
241 } else {
242 V4.install(loader, files);
243 }
244 }
245 }
246
247 /**
248 * Returns whether all files in the list are valid zip files. If {@code files} is empty, then
249 * returns true.
250 */
251 private static boolean checkValidZipFiles(List<File> files) {
252 for (File file : files) {
253 if (!MultiDexExtractor.verifyZipFile(file)) {
254 return false;
255 }
256 }
257 return true;
258 }
259
Maurice Chu667f9a82013-10-16 13:12:22 -0700260 /**
261 * Locates a given field anywhere in the class inheritance hierarchy.
262 *
263 * @param instance an object to search the field into.
264 * @param name field name
265 * @return a field object
266 * @throws NoSuchFieldException if the field cannot be located
267 */
268 private static Field findField(Object instance, String name) throws NoSuchFieldException {
269 for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
270 try {
271 Field field = clazz.getDeclaredField(name);
272
273
274 if (!field.isAccessible()) {
275 field.setAccessible(true);
276 }
277
278 return field;
279 } catch (NoSuchFieldException e) {
280 // ignore and search next
281 }
282 }
283
284 throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
285 }
286
287 /**
288 * Locates a given method anywhere in the class inheritance hierarchy.
289 *
290 * @param instance an object to search the method into.
291 * @param name method name
292 * @param parameterTypes method parameter types
293 * @return a method object
294 * @throws NoSuchMethodException if the method cannot be located
295 */
296 private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
297 throws NoSuchMethodException {
298 for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
299 try {
300 Method method = clazz.getDeclaredMethod(name, parameterTypes);
301
302
303 if (!method.isAccessible()) {
304 method.setAccessible(true);
305 }
306
307 return method;
308 } catch (NoSuchMethodException e) {
309 // ignore and search next
310 }
311 }
312
313 throw new NoSuchMethodException("Method " + name + " with parameters " +
314 Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
315 }
316
317 /**
318 * Replace the value of a field containing a non null array, by a new array containing the
319 * elements of the original array plus the elements of extraElements.
320 * @param instance the instance whose field is to be modified.
321 * @param fieldName the field to modify.
322 * @param extraElements elements to append at the end of the array.
323 */
324 private static void expandFieldArray(Object instance, String fieldName,
325 Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
326 IllegalAccessException {
327 Field jlrField = findField(instance, fieldName);
328 Object[] original = (Object[]) jlrField.get(instance);
329 Object[] combined = (Object[]) Array.newInstance(
330 original.getClass().getComponentType(), original.length + extraElements.length);
331 System.arraycopy(original, 0, combined, 0, original.length);
332 System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
333 jlrField.set(instance, combined);
334 }
335
Yohann Rousseld79604b2014-07-08 16:50:10 +0200336 private static void clearOldDexDir(Context context) throws Exception {
Yohann Roussel590a07e2014-07-21 17:47:26 +0200337 File dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME);
Yohann Rousseld79604b2014-07-08 16:50:10 +0200338 if (dexDir.isDirectory()) {
339 Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ").");
340 File[] files = dexDir.listFiles();
341 if (files == null) {
342 Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
343 return;
344 }
345 for (File oldFile : files) {
346 Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size "
347 + oldFile.length());
348 if (!oldFile.delete()) {
349 Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
350 } else {
351 Log.i(TAG, "Deleted old file " + oldFile.getPath());
352 }
353 }
354 if (!dexDir.delete()) {
355 Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath());
356 } else {
357 Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath());
358 }
359 }
360 }
361
Yohann Roussel606af942015-05-12 17:40:52 +0200362 private static File getDexDir(Context context, ApplicationInfo applicationInfo)
363 throws IOException {
364 File cache = new File(applicationInfo.dataDir, CODE_CACHE_NAME);
365 try {
366 mkdirChecked(cache);
367 } catch (IOException e) {
368 /* If we can't emulate code_cache, then store to filesDir. This means abandoning useless
369 * files on disk if the device ever updates to android 5+. But since this seems to
370 * happen only on some devices running android 2, this should cause no pollution.
371 */
372 cache = new File(context.getFilesDir(), CODE_CACHE_NAME);
373 mkdirChecked(cache);
374 }
375 File dexDir = new File(cache, CODE_CACHE_SECONDARY_FOLDER_NAME);
376 mkdirChecked(dexDir);
377 return dexDir;
378 }
379
380 private static void mkdirChecked(File dir) throws IOException {
381 dir.mkdir();
382 if (!dir.isDirectory()) {
383 File parent = dir.getParentFile();
384 if (parent == null) {
385 Log.e(TAG, "Failed to create dir " + dir.getPath() + ". Parent file is null.");
386 } else {
387 Log.e(TAG, "Failed to create dir " + dir.getPath() +
388 ". parent file is a dir " + parent.isDirectory() +
389 ", a file " + parent.isFile() +
390 ", exists " + parent.exists() +
391 ", readable " + parent.canRead() +
392 ", writable " + parent.canWrite());
393 }
394 throw new IOException("Failed to create directory " + dir.getPath());
395 }
396 }
397
Maurice Chu667f9a82013-10-16 13:12:22 -0700398 /**
399 * Installer for platform versions 19.
400 */
401 private static final class V19 {
402
403 private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
404 File optimizedDirectory)
405 throws IllegalArgumentException, IllegalAccessException,
406 NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
407 /* The patched class loader is expected to be a descendant of
408 * dalvik.system.BaseDexClassLoader. We modify its
409 * dalvik.system.DexPathList pathList field to append additional DEX
410 * file entries.
411 */
412 Field pathListField = findField(loader, "pathList");
413 Object dexPathList = pathListField.get(loader);
414 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
415 expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
416 new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
417 suppressedExceptions));
418 if (suppressedExceptions.size() > 0) {
Yohann Roussel88117c32013-11-28 23:22:11 +0100419 for (IOException e : suppressedExceptions) {
420 Log.w(TAG, "Exception in makeDexElement", e);
421 }
Maurice Chu667f9a82013-10-16 13:12:22 -0700422 Field suppressedExceptionsField =
Yohann Roussel74e66b82016-05-17 18:00:18 +0200423 findField(dexPathList, "dexElementsSuppressedExceptions");
Maurice Chu667f9a82013-10-16 13:12:22 -0700424 IOException[] dexElementsSuppressedExceptions =
Yohann Roussel74e66b82016-05-17 18:00:18 +0200425 (IOException[]) suppressedExceptionsField.get(dexPathList);
Maurice Chu667f9a82013-10-16 13:12:22 -0700426
427 if (dexElementsSuppressedExceptions == null) {
428 dexElementsSuppressedExceptions =
429 suppressedExceptions.toArray(
430 new IOException[suppressedExceptions.size()]);
431 } else {
432 IOException[] combined =
433 new IOException[suppressedExceptions.size() +
434 dexElementsSuppressedExceptions.length];
435 suppressedExceptions.toArray(combined);
436 System.arraycopy(dexElementsSuppressedExceptions, 0, combined,
437 suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
438 dexElementsSuppressedExceptions = combined;
439 }
440
Yohann Roussel74e66b82016-05-17 18:00:18 +0200441 suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
Maurice Chu667f9a82013-10-16 13:12:22 -0700442 }
443 }
444
445 /**
446 * A wrapper around
447 * {@code private static final dalvik.system.DexPathList#makeDexElements}.
448 */
449 private static Object[] makeDexElements(
450 Object dexPathList, ArrayList<File> files, File optimizedDirectory,
451 ArrayList<IOException> suppressedExceptions)
452 throws IllegalAccessException, InvocationTargetException,
453 NoSuchMethodException {
454 Method makeDexElements =
455 findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
456 ArrayList.class);
457
458 return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
459 suppressedExceptions);
460 }
461 }
462
463 /**
464 * Installer for platform versions 14, 15, 16, 17 and 18.
465 */
466 private static final class V14 {
467
468 private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
469 File optimizedDirectory)
470 throws IllegalArgumentException, IllegalAccessException,
471 NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
472 /* The patched class loader is expected to be a descendant of
473 * dalvik.system.BaseDexClassLoader. We modify its
474 * dalvik.system.DexPathList pathList field to append additional DEX
475 * file entries.
476 */
477 Field pathListField = findField(loader, "pathList");
478 Object dexPathList = pathListField.get(loader);
479 expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
480 new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
481 }
482
483 /**
484 * A wrapper around
485 * {@code private static final dalvik.system.DexPathList#makeDexElements}.
486 */
487 private static Object[] makeDexElements(
488 Object dexPathList, ArrayList<File> files, File optimizedDirectory)
489 throws IllegalAccessException, InvocationTargetException,
490 NoSuchMethodException {
491 Method makeDexElements =
492 findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
493
494 return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
495 }
496 }
497
498 /**
499 * Installer for platform versions 4 to 13.
500 */
501 private static final class V4 {
Yohann Roussel52eafa02013-11-21 11:46:53 +0100502 private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
Maurice Chu667f9a82013-10-16 13:12:22 -0700503 throws IllegalArgumentException, IllegalAccessException,
504 NoSuchFieldException, IOException {
505 /* The patched class loader is expected to be a descendant of
506 * dalvik.system.DexClassLoader. We modify its
Yohann Roussel52eafa02013-11-21 11:46:53 +0100507 * fields mPaths, mFiles, mZips and mDexs to append additional DEX
Maurice Chu667f9a82013-10-16 13:12:22 -0700508 * file entries.
509 */
510 int extraSize = additionalClassPathEntries.size();
511
512 Field pathField = findField(loader, "path");
513
514 StringBuilder path = new StringBuilder((String) pathField.get(loader));
515 String[] extraPaths = new String[extraSize];
516 File[] extraFiles = new File[extraSize];
Maurice Chu66f379f2013-11-14 19:08:32 -0800517 ZipFile[] extraZips = new ZipFile[extraSize];
Maurice Chu667f9a82013-10-16 13:12:22 -0700518 DexFile[] extraDexs = new DexFile[extraSize];
519 for (ListIterator<File> iterator = additionalClassPathEntries.listIterator();
520 iterator.hasNext();) {
521 File additionalEntry = iterator.next();
522 String entryPath = additionalEntry.getAbsolutePath();
523 path.append(':').append(entryPath);
524 int index = iterator.previousIndex();
525 extraPaths[index] = entryPath;
526 extraFiles[index] = additionalEntry;
Maurice Chu66f379f2013-11-14 19:08:32 -0800527 extraZips[index] = new ZipFile(additionalEntry);
Maurice Chu667f9a82013-10-16 13:12:22 -0700528 extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0);
529 }
530
531 pathField.set(loader, path.toString());
532 expandFieldArray(loader, "mPaths", extraPaths);
533 expandFieldArray(loader, "mFiles", extraFiles);
Maurice Chu66f379f2013-11-14 19:08:32 -0800534 expandFieldArray(loader, "mZips", extraZips);
Maurice Chu667f9a82013-10-16 13:12:22 -0700535 expandFieldArray(loader, "mDexs", extraDexs);
536 }
537 }
538
539}