blob: 982bfbf777d92f709f83369e8fdbc761a8c6ab9c [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;
22import android.content.pm.PackageManager;
Yohann Rousseld79604b2014-07-08 16:50:10 +020023import android.content.pm.PackageManager.NameNotFoundException;
Maurice Chu667f9a82013-10-16 13:12:22 -070024import android.os.Build;
25import android.util.Log;
26
Yohann Roussel602c6ca2014-03-28 17:35:02 +010027import dalvik.system.DexFile;
28
Maurice Chu667f9a82013-10-16 13:12:22 -070029import java.io.File;
30import java.io.IOException;
31import java.lang.reflect.Array;
32import java.lang.reflect.Field;
33import java.lang.reflect.InvocationTargetException;
34import java.lang.reflect.Method;
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.HashSet;
38import java.util.List;
39import java.util.ListIterator;
40import java.util.Set;
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020041import java.util.regex.Matcher;
42import java.util.regex.Pattern;
Maurice Chu66f379f2013-11-14 19:08:32 -080043import java.util.zip.ZipFile;
Maurice Chu667f9a82013-10-16 13:12:22 -070044
45/**
46 * Monkey patches {@link Context#getClassLoader() the application context class
47 * loader} in order to load classes from more than one dex file. The primary
Yohann Rousseldd3cc222014-04-22 10:47:53 +020048 * {@code classes.dex} must contain the classes necessary for calling this
49 * class methods. Secondary dex files named classes2.dex, classes3.dex... found
50 * in the application apk will be added to the classloader after first call to
Maurice Chu667f9a82013-10-16 13:12:22 -070051 * {@link #install(Context)}.
52 *
53 * <p/>
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020054 * This library provides compatibility for platforms with API level 4 through 20. This library does
55 * nothing on newer versions of the platform which provide built-in support for secondary dex files.
Maurice Chu667f9a82013-10-16 13:12:22 -070056 */
57public final class MultiDex {
58
59 static final String TAG = "MultiDex";
60
Yohann Roussel590a07e2014-07-21 17:47:26 +020061 private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes";
62
Yohann Roussel606af942015-05-12 17:40:52 +020063 private static final String CODE_CACHE_NAME = "code_cache";
64
65 private static final String CODE_CACHE_SECONDARY_FOLDER_NAME = "secondary-dexes";
Maurice Chu667f9a82013-10-16 13:12:22 -070066
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020067 private static final int MAX_SUPPORTED_SDK_VERSION = 20;
Maurice Chu667f9a82013-10-16 13:12:22 -070068
69 private static final int MIN_SDK_VERSION = 4;
70
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020071 private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
72
73 private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
74
Maurice Chu667f9a82013-10-16 13:12:22 -070075 private static final Set<String> installedApk = new HashSet<String>();
76
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020077 private static final boolean IS_VM_MULTIDEX_CAPABLE =
78 isVMMultidexCapable(System.getProperty("java.vm.version"));
79
Maurice Chu667f9a82013-10-16 13:12:22 -070080 private MultiDex() {}
81
82 /**
83 * Patches the application context class loader by appending extra dex files
Yohann Rousseldd3cc222014-04-22 10:47:53 +020084 * loaded from the application apk. This method should be called in the
85 * attachBaseContext of your {@link Application}, see
86 * {@link MultiDexApplication} for more explanation and an example.
Maurice Chu667f9a82013-10-16 13:12:22 -070087 *
88 * @param context application context.
89 * @throws RuntimeException if an error occurred preventing the classloader
90 * extension.
91 */
92 public static void install(Context context) {
Yohann Roussel602c6ca2014-03-28 17:35:02 +010093 Log.i(TAG, "install");
Yohann Roussel7b86f7e2014-04-22 11:12:54 +020094 if (IS_VM_MULTIDEX_CAPABLE) {
95 Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
96 return;
97 }
Maurice Chu667f9a82013-10-16 13:12:22 -070098
99 if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
100 throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT
101 + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
102 }
103
Maurice Chu667f9a82013-10-16 13:12:22 -0700104 try {
Yohann Rousseld79604b2014-07-08 16:50:10 +0200105 ApplicationInfo applicationInfo = getApplicationInfo(context);
Maurice Chu667f9a82013-10-16 13:12:22 -0700106 if (applicationInfo == null) {
Yohann Rousseld79604b2014-07-08 16:50:10 +0200107 // Looks like running on a test Context, so just return without patching.
Maurice Chu667f9a82013-10-16 13:12:22 -0700108 return;
109 }
110
111 synchronized (installedApk) {
112 String apkPath = applicationInfo.sourceDir;
113 if (installedApk.contains(apkPath)) {
114 return;
115 }
116 installedApk.add(apkPath);
117
Yohann Roussel7b86f7e2014-04-22 11:12:54 +0200118 if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
119 Log.w(TAG, "MultiDex is not guaranteed to work in SDK version "
120 + Build.VERSION.SDK_INT + ": SDK version higher than "
121 + MAX_SUPPORTED_SDK_VERSION + " should be backed by "
122 + "runtime with built-in multidex capabilty but it's not the "
123 + "case here: java.vm.version=\""
124 + System.getProperty("java.vm.version") + "\"");
Maurice Chu667f9a82013-10-16 13:12:22 -0700125 }
126
127 /* The patched class loader is expected to be a descendant of
128 * dalvik.system.BaseDexClassLoader. We modify its
129 * dalvik.system.DexPathList pathList field to append additional DEX
130 * file entries.
131 */
132 ClassLoader loader;
133 try {
134 loader = context.getClassLoader();
135 } catch (RuntimeException e) {
136 /* Ignore those exceptions so that we don't break tests relying on Context like
137 * a android.test.mock.MockContext or a android.content.ContextWrapper with a
138 * null base Context.
139 */
140 Log.w(TAG, "Failure while trying to obtain Context class loader. " +
141 "Must be running in test mode. Skip patching.", e);
142 return;
143 }
144 if (loader == null) {
145 // Note, the context class loader is null when running Robolectric tests.
146 Log.e(TAG,
147 "Context class loader is null. Must be running in test mode. "
148 + "Skip patching.");
149 return;
150 }
151
Yohann Roussel590a07e2014-07-21 17:47:26 +0200152 try {
153 clearOldDexDir(context);
154 } catch (Throwable t) {
155 Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
156 + "continuing without cleaning.", t);
157 }
158
Yohann Roussel606af942015-05-12 17:40:52 +0200159 File dexDir = getDexDir(context, applicationInfo);
Maurice Chu7e267a32014-01-15 19:02:18 -0800160 List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
Maurice Chucc63eda2013-12-02 15:39:59 -0800161 if (checkValidZipFiles(files)) {
162 installSecondaryDexes(loader, dexDir, files);
163 } else {
164 Log.w(TAG, "Files were not valid zip files. Forcing a reload.");
165 // Try again, but this time force a reload of the zip file.
Maurice Chu7e267a32014-01-15 19:02:18 -0800166 files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
Yohann Roussel602c6ca2014-03-28 17:35:02 +0100167
Maurice Chucc63eda2013-12-02 15:39:59 -0800168 if (checkValidZipFiles(files)) {
169 installSecondaryDexes(loader, dexDir, files);
Maurice Chu667f9a82013-10-16 13:12:22 -0700170 } else {
Maurice Chucc63eda2013-12-02 15:39:59 -0800171 // Second time didn't work, give up
172 throw new RuntimeException("Zip files were not valid.");
Maurice Chu667f9a82013-10-16 13:12:22 -0700173 }
174 }
175 }
176
177 } catch (Exception e) {
178 Log.e(TAG, "Multidex installation failure", e);
179 throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");
180 }
Yohann Roussel602c6ca2014-03-28 17:35:02 +0100181 Log.i(TAG, "install done");
Maurice Chu667f9a82013-10-16 13:12:22 -0700182 }
183
Yohann Rousseld79604b2014-07-08 16:50:10 +0200184 private static ApplicationInfo getApplicationInfo(Context context)
185 throws NameNotFoundException {
186 PackageManager pm;
187 String packageName;
188 try {
189 pm = context.getPackageManager();
190 packageName = context.getPackageName();
191 } catch (RuntimeException e) {
192 /* Ignore those exceptions so that we don't break tests relying on Context like
193 * a android.test.mock.MockContext or a android.content.ContextWrapper with a null
194 * base Context.
195 */
196 Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " +
197 "Must be running in test mode. Skip patching.", e);
198 return null;
199 }
200 if (pm == null || packageName == null) {
201 // This is most likely a mock context, so just return without patching.
202 return null;
203 }
204 ApplicationInfo applicationInfo =
205 pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
206 return applicationInfo;
207 }
208
Yohann Roussel7b86f7e2014-04-22 11:12:54 +0200209 /**
210 * Identifies if the current VM has a native support for multidex, meaning there is no need for
211 * additional installation by this library.
212 * @return true if the VM handles multidex
213 */
214 /* package visible for test */
215 static boolean isVMMultidexCapable(String versionString) {
216 boolean isMultidexCapable = false;
217 if (versionString != null) {
218 Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
219 if (matcher.matches()) {
220 try {
221 int major = Integer.parseInt(matcher.group(1));
222 int minor = Integer.parseInt(matcher.group(2));
223 isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
224 || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
225 && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
226 } catch (NumberFormatException e) {
227 // let isMultidexCapable be false
228 }
229 }
230 }
231 Log.i(TAG, "VM with version " + versionString +
232 (isMultidexCapable ?
233 " has multidex support" :
234 " does not have multidex support"));
235 return isMultidexCapable;
236 }
237
Maurice Chucc63eda2013-12-02 15:39:59 -0800238 private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)
239 throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
240 InvocationTargetException, NoSuchMethodException, IOException {
241 if (!files.isEmpty()) {
242 if (Build.VERSION.SDK_INT >= 19) {
243 V19.install(loader, files, dexDir);
244 } else if (Build.VERSION.SDK_INT >= 14) {
245 V14.install(loader, files, dexDir);
246 } else {
247 V4.install(loader, files);
248 }
249 }
250 }
251
252 /**
253 * Returns whether all files in the list are valid zip files. If {@code files} is empty, then
254 * returns true.
255 */
256 private static boolean checkValidZipFiles(List<File> files) {
257 for (File file : files) {
258 if (!MultiDexExtractor.verifyZipFile(file)) {
259 return false;
260 }
261 }
262 return true;
263 }
264
Maurice Chu667f9a82013-10-16 13:12:22 -0700265 /**
266 * Locates a given field anywhere in the class inheritance hierarchy.
267 *
268 * @param instance an object to search the field into.
269 * @param name field name
270 * @return a field object
271 * @throws NoSuchFieldException if the field cannot be located
272 */
273 private static Field findField(Object instance, String name) throws NoSuchFieldException {
274 for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
275 try {
276 Field field = clazz.getDeclaredField(name);
277
278
279 if (!field.isAccessible()) {
280 field.setAccessible(true);
281 }
282
283 return field;
284 } catch (NoSuchFieldException e) {
285 // ignore and search next
286 }
287 }
288
289 throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
290 }
291
292 /**
293 * Locates a given method anywhere in the class inheritance hierarchy.
294 *
295 * @param instance an object to search the method into.
296 * @param name method name
297 * @param parameterTypes method parameter types
298 * @return a method object
299 * @throws NoSuchMethodException if the method cannot be located
300 */
301 private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
302 throws NoSuchMethodException {
303 for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
304 try {
305 Method method = clazz.getDeclaredMethod(name, parameterTypes);
306
307
308 if (!method.isAccessible()) {
309 method.setAccessible(true);
310 }
311
312 return method;
313 } catch (NoSuchMethodException e) {
314 // ignore and search next
315 }
316 }
317
318 throw new NoSuchMethodException("Method " + name + " with parameters " +
319 Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
320 }
321
322 /**
323 * Replace the value of a field containing a non null array, by a new array containing the
324 * elements of the original array plus the elements of extraElements.
325 * @param instance the instance whose field is to be modified.
326 * @param fieldName the field to modify.
327 * @param extraElements elements to append at the end of the array.
328 */
329 private static void expandFieldArray(Object instance, String fieldName,
330 Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
331 IllegalAccessException {
332 Field jlrField = findField(instance, fieldName);
333 Object[] original = (Object[]) jlrField.get(instance);
334 Object[] combined = (Object[]) Array.newInstance(
335 original.getClass().getComponentType(), original.length + extraElements.length);
336 System.arraycopy(original, 0, combined, 0, original.length);
337 System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
338 jlrField.set(instance, combined);
339 }
340
Yohann Rousseld79604b2014-07-08 16:50:10 +0200341 private static void clearOldDexDir(Context context) throws Exception {
Yohann Roussel590a07e2014-07-21 17:47:26 +0200342 File dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME);
Yohann Rousseld79604b2014-07-08 16:50:10 +0200343 if (dexDir.isDirectory()) {
344 Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ").");
345 File[] files = dexDir.listFiles();
346 if (files == null) {
347 Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
348 return;
349 }
350 for (File oldFile : files) {
351 Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size "
352 + oldFile.length());
353 if (!oldFile.delete()) {
354 Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
355 } else {
356 Log.i(TAG, "Deleted old file " + oldFile.getPath());
357 }
358 }
359 if (!dexDir.delete()) {
360 Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath());
361 } else {
362 Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath());
363 }
364 }
365 }
366
Yohann Roussel606af942015-05-12 17:40:52 +0200367 private static File getDexDir(Context context, ApplicationInfo applicationInfo)
368 throws IOException {
369 File cache = new File(applicationInfo.dataDir, CODE_CACHE_NAME);
370 try {
371 mkdirChecked(cache);
372 } catch (IOException e) {
373 /* If we can't emulate code_cache, then store to filesDir. This means abandoning useless
374 * files on disk if the device ever updates to android 5+. But since this seems to
375 * happen only on some devices running android 2, this should cause no pollution.
376 */
377 cache = new File(context.getFilesDir(), CODE_CACHE_NAME);
378 mkdirChecked(cache);
379 }
380 File dexDir = new File(cache, CODE_CACHE_SECONDARY_FOLDER_NAME);
381 mkdirChecked(dexDir);
382 return dexDir;
383 }
384
385 private static void mkdirChecked(File dir) throws IOException {
386 dir.mkdir();
387 if (!dir.isDirectory()) {
388 File parent = dir.getParentFile();
389 if (parent == null) {
390 Log.e(TAG, "Failed to create dir " + dir.getPath() + ". Parent file is null.");
391 } else {
392 Log.e(TAG, "Failed to create dir " + dir.getPath() +
393 ". parent file is a dir " + parent.isDirectory() +
394 ", a file " + parent.isFile() +
395 ", exists " + parent.exists() +
396 ", readable " + parent.canRead() +
397 ", writable " + parent.canWrite());
398 }
399 throw new IOException("Failed to create directory " + dir.getPath());
400 }
401 }
402
Maurice Chu667f9a82013-10-16 13:12:22 -0700403 /**
404 * Installer for platform versions 19.
405 */
406 private static final class V19 {
407
408 private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
409 File optimizedDirectory)
410 throws IllegalArgumentException, IllegalAccessException,
411 NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
412 /* The patched class loader is expected to be a descendant of
413 * dalvik.system.BaseDexClassLoader. We modify its
414 * dalvik.system.DexPathList pathList field to append additional DEX
415 * file entries.
416 */
417 Field pathListField = findField(loader, "pathList");
418 Object dexPathList = pathListField.get(loader);
419 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
420 expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
421 new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
422 suppressedExceptions));
423 if (suppressedExceptions.size() > 0) {
Yohann Roussel88117c32013-11-28 23:22:11 +0100424 for (IOException e : suppressedExceptions) {
425 Log.w(TAG, "Exception in makeDexElement", e);
426 }
Maurice Chu667f9a82013-10-16 13:12:22 -0700427 Field suppressedExceptionsField =
Yohann Roussel74e66b82016-05-17 18:00:18 +0200428 findField(dexPathList, "dexElementsSuppressedExceptions");
Maurice Chu667f9a82013-10-16 13:12:22 -0700429 IOException[] dexElementsSuppressedExceptions =
Yohann Roussel74e66b82016-05-17 18:00:18 +0200430 (IOException[]) suppressedExceptionsField.get(dexPathList);
Maurice Chu667f9a82013-10-16 13:12:22 -0700431
432 if (dexElementsSuppressedExceptions == null) {
433 dexElementsSuppressedExceptions =
434 suppressedExceptions.toArray(
435 new IOException[suppressedExceptions.size()]);
436 } else {
437 IOException[] combined =
438 new IOException[suppressedExceptions.size() +
439 dexElementsSuppressedExceptions.length];
440 suppressedExceptions.toArray(combined);
441 System.arraycopy(dexElementsSuppressedExceptions, 0, combined,
442 suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
443 dexElementsSuppressedExceptions = combined;
444 }
445
Yohann Roussel74e66b82016-05-17 18:00:18 +0200446 suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
Maurice Chu667f9a82013-10-16 13:12:22 -0700447 }
448 }
449
450 /**
451 * A wrapper around
452 * {@code private static final dalvik.system.DexPathList#makeDexElements}.
453 */
454 private static Object[] makeDexElements(
455 Object dexPathList, ArrayList<File> files, File optimizedDirectory,
456 ArrayList<IOException> suppressedExceptions)
457 throws IllegalAccessException, InvocationTargetException,
458 NoSuchMethodException {
459 Method makeDexElements =
460 findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
461 ArrayList.class);
462
463 return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
464 suppressedExceptions);
465 }
466 }
467
468 /**
469 * Installer for platform versions 14, 15, 16, 17 and 18.
470 */
471 private static final class V14 {
472
473 private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
474 File optimizedDirectory)
475 throws IllegalArgumentException, IllegalAccessException,
476 NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
477 /* The patched class loader is expected to be a descendant of
478 * dalvik.system.BaseDexClassLoader. We modify its
479 * dalvik.system.DexPathList pathList field to append additional DEX
480 * file entries.
481 */
482 Field pathListField = findField(loader, "pathList");
483 Object dexPathList = pathListField.get(loader);
484 expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
485 new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
486 }
487
488 /**
489 * A wrapper around
490 * {@code private static final dalvik.system.DexPathList#makeDexElements}.
491 */
492 private static Object[] makeDexElements(
493 Object dexPathList, ArrayList<File> files, File optimizedDirectory)
494 throws IllegalAccessException, InvocationTargetException,
495 NoSuchMethodException {
496 Method makeDexElements =
497 findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
498
499 return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
500 }
501 }
502
503 /**
504 * Installer for platform versions 4 to 13.
505 */
506 private static final class V4 {
Yohann Roussel52eafa02013-11-21 11:46:53 +0100507 private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
Maurice Chu667f9a82013-10-16 13:12:22 -0700508 throws IllegalArgumentException, IllegalAccessException,
509 NoSuchFieldException, IOException {
510 /* The patched class loader is expected to be a descendant of
511 * dalvik.system.DexClassLoader. We modify its
Yohann Roussel52eafa02013-11-21 11:46:53 +0100512 * fields mPaths, mFiles, mZips and mDexs to append additional DEX
Maurice Chu667f9a82013-10-16 13:12:22 -0700513 * file entries.
514 */
515 int extraSize = additionalClassPathEntries.size();
516
517 Field pathField = findField(loader, "path");
518
519 StringBuilder path = new StringBuilder((String) pathField.get(loader));
520 String[] extraPaths = new String[extraSize];
521 File[] extraFiles = new File[extraSize];
Maurice Chu66f379f2013-11-14 19:08:32 -0800522 ZipFile[] extraZips = new ZipFile[extraSize];
Maurice Chu667f9a82013-10-16 13:12:22 -0700523 DexFile[] extraDexs = new DexFile[extraSize];
524 for (ListIterator<File> iterator = additionalClassPathEntries.listIterator();
525 iterator.hasNext();) {
526 File additionalEntry = iterator.next();
527 String entryPath = additionalEntry.getAbsolutePath();
528 path.append(':').append(entryPath);
529 int index = iterator.previousIndex();
530 extraPaths[index] = entryPath;
531 extraFiles[index] = additionalEntry;
Maurice Chu66f379f2013-11-14 19:08:32 -0800532 extraZips[index] = new ZipFile(additionalEntry);
Maurice Chu667f9a82013-10-16 13:12:22 -0700533 extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0);
534 }
535
536 pathField.set(loader, path.toString());
537 expandFieldArray(loader, "mPaths", extraPaths);
538 expandFieldArray(loader, "mFiles", extraFiles);
Maurice Chu66f379f2013-11-14 19:08:32 -0800539 expandFieldArray(loader, "mZips", extraZips);
Maurice Chu667f9a82013-10-16 13:12:22 -0700540 expandFieldArray(loader, "mDexs", extraDexs);
541 }
542 }
543
544}