Home | History | Annotate | Download | only in fakelibrary
      1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
      2 // for details. All rights reserved. Use of this source code is governed by a
      3 // BSD-style license that can be found in the LICENSE file.
      4 
      5 package multidex004.fakelibrary;
      6 
      7 
      8 import multidex004.fakeframeworks.Application;
      9 import multidex004.fakeframeworks.Context;
     10 
     11 import java.io.File;
     12 import java.io.IOException;
     13 import java.lang.reflect.Field;
     14 import java.lang.reflect.InvocationTargetException;
     15 import java.lang.reflect.Method;
     16 import java.util.List;
     17 import java.util.ListIterator;
     18 import java.util.Random;
     19 import java.util.Set;
     20 
     21 /**
     22  * Monkey patches {@link Context#getClassLoader() the application context class
     23  * loader} in order to load classes from more than one dex file. The primary
     24  * {@code classes.dex} must contain the classes necessary for calling this
     25  * class methods. Secondary dex files named classes2.dex, classes3.dex... found
     26  * in the application apk will be added to the classloader after first call to
     27  * {@link #install(Context)}.
     28  *
     29  * <p/>
     30  * This library provides compatibility for platforms with API level 4 through 20. This library does
     31  * nothing on newer versions of the platform which provide built-in support for secondary dex files.
     32  */
     33 public final class MultiDex {
     34 
     35     static final String TAG = "MultiDex";
     36 
     37     private static final String SECONDARY_FOLDER_NAME = "secondary-dexes";
     38 
     39     private static final int MAX_SUPPORTED_SDK_VERSION = 20;
     40 
     41     private static final int MIN_SDK_VERSION = 4;
     42 
     43     private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
     44 
     45     private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
     46 
     47     private static final Set<String> installedApk = null;
     48 
     49     private static final boolean IS_VM_MULTIDEX_CAPABLE =
     50             isVMMultidexCapable(System.getProperty("java.vm.version"));
     51 
     52     private MultiDex() {}
     53 
     54     /**
     55      * Patches the application context class loader by appending extra dex files
     56      * loaded from the application apk. This method should be called in the
     57      * attachBaseContext of your {@link Application}, see
     58      * {@link MultiDexApplication} for more explanation and an example.
     59      *
     60      * @param context application context.
     61      * @throws RuntimeException if an error occurred preventing the classloader
     62      *         extension.
     63      */
     64     public static void install(Context context) {
     65         if (IS_VM_MULTIDEX_CAPABLE) {
     66             try {
     67                 clearOldDexDir(context);
     68             } catch (Throwable t) {
     69             }
     70             return;
     71         }
     72      }
     73 
     74     /**
     75      * Identifies if the current VM has a native support for multidex, meaning there is no need for
     76      * additional installation by this library.
     77      * @return true if the VM handles multidex
     78      */
     79     /* package visible for test */
     80     static boolean isVMMultidexCapable(String versionString) {
     81         boolean isMultidexCapable = false;
     82         if (versionString != null) {
     83         }
     84         return isMultidexCapable;
     85     }
     86 
     87     private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)
     88             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
     89             InvocationTargetException, NoSuchMethodException, IOException {
     90       int version = new Random().nextInt(23);
     91         if (!files.isEmpty()) {
     92             if (version >= 19) {
     93                 V19.install(loader, files, dexDir);
     94             } else if (version >= 14) {
     95                 V14.install(loader, files, dexDir);
     96             } else {
     97                 V4.install(loader, files);
     98             }
     99         }
    100     }
    101 
    102     /**
    103      * Returns whether all files in the list are valid zip files.  If {@code files} is empty, then
    104      * returns true.
    105      */
    106     private static boolean checkValidZipFiles(List<File> files) {
    107         for (File file : files) {
    108         }
    109         return true;
    110     }
    111 
    112     /**
    113      * Locates a given field anywhere in the class inheritance hierarchy.
    114      *
    115      * @param instance an object to search the field into.
    116      * @param name field name
    117      * @return a field object
    118      * @throws NoSuchFieldException if the field cannot be located
    119      */
    120     private static Field findField(Object instance, String name) throws NoSuchFieldException {
    121         for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
    122             try {
    123                 Field field = clazz.getDeclaredField(name);
    124 
    125 
    126                 if (!field.isAccessible()) {
    127                     field.setAccessible(true);
    128                 }
    129 
    130                 return field;
    131             } catch (NoSuchFieldException e) {
    132                 // ignore and search next
    133             }
    134         }
    135 
    136         throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    137     }
    138 
    139     /**
    140      * Locates a given method anywhere in the class inheritance hierarchy.
    141      *
    142      * @param instance an object to search the method into.
    143      * @param name method name
    144      * @param parameterTypes method parameter types
    145      * @return a method object
    146      * @throws NoSuchMethodException if the method cannot be located
    147      */
    148     private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
    149             throws NoSuchMethodException {
    150         for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
    151             try {
    152                 Method method = clazz.getDeclaredMethod(name, parameterTypes);
    153 
    154 
    155                 if (!method.isAccessible()) {
    156                     method.setAccessible(true);
    157                 }
    158 
    159                 return method;
    160             } catch (NoSuchMethodException e) {
    161                 // ignore and search next
    162             }
    163         }
    164 
    165         throw new NoSuchMethodException("Method " + name + " with parameters " +
    166                 parameterTypes + " not found in " + instance.getClass());
    167     }
    168 
    169     /**
    170      * Replace the value of a field containing a non null array, by a new array containing the
    171      * elements of the original array plus the elements of extraElements.
    172      * @param instance the instance whose field is to be modified.
    173      * @param fieldName the field to modify.
    174      * @param extraElements elements to append at the end of the array.
    175      */
    176     private static void expandFieldArray(Object instance, String fieldName,
    177             Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
    178             IllegalAccessException {
    179         Field jlrField = findField(instance, fieldName);
    180         Object[] original = (Object[]) jlrField.get(instance);
    181         Object[] combined = (Object[]) null;
    182         System.arraycopy(original, 0, combined, 0, original.length);
    183         System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
    184         jlrField.set(instance, combined);
    185     }
    186 
    187     private static void clearOldDexDir(Context context) throws Exception {
    188 
    189      }
    190 
    191     /**
    192      * Installer for platform versions 19.
    193      */
    194     private static final class V19 {
    195 
    196         private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
    197                 File optimizedDirectory)
    198                         throws IllegalArgumentException, IllegalAccessException,
    199                         NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
    200             /* The patched class loader is expected to be a descendant of
    201              * dalvik.system.BaseDexClassLoader. We modify its
    202              * dalvik.system.DexPathList pathList field to append additional DEX
    203              * file entries.
    204              */
    205             Field pathListField = findField(loader, "pathList");
    206             Object dexPathList = pathListField.get(loader);
    207         }
    208 
    209     }
    210     /**
    211      * Installer for platform versions 14, 15, 16, 17 and 18.
    212      */
    213     private static final class V14 {
    214 
    215         private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
    216                 File optimizedDirectory)
    217                         throws IllegalArgumentException, IllegalAccessException,
    218                         NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
    219             /* The patched class loader is expected to be a descendant of
    220              * dalvik.system.BaseDexClassLoader. We modify its
    221              * dalvik.system.DexPathList pathList field to append additional DEX
    222              * file entries.
    223              */
    224             Field pathListField = findField(loader, "pathList");
    225             Object dexPathList = pathListField.get(loader);
    226         }
    227 
    228      }
    229 
    230     /**
    231      * Installer for platform versions 4 to 13.
    232      */
    233     private static final class V4 {
    234         private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
    235                         throws IllegalArgumentException, IllegalAccessException,
    236                         NoSuchFieldException, IOException {
    237             /* The patched class loader is expected to be a descendant of
    238              * dalvik.system.DexClassLoader. We modify its
    239              * fields mPaths, mFiles, mZips and mDexs to append additional DEX
    240              * file entries.
    241              */
    242             int extraSize = additionalClassPathEntries.size();
    243 
    244             Field pathField = findField(loader, "path");
    245 
    246             StringBuilder path = new StringBuilder((String) pathField.get(loader));
    247             String[] extraPaths = new String[extraSize];
    248             File[] extraFiles = new File[extraSize];
    249             for (ListIterator<File> iterator = additionalClassPathEntries.listIterator();
    250                     iterator.hasNext();) {
    251                 File additionalEntry = iterator.next();
    252                 String entryPath = additionalEntry.getAbsolutePath();
    253                 path.append(':').append(entryPath);
    254                 int index = iterator.previousIndex();
    255                 extraPaths[index] = entryPath;
    256                 extraFiles[index] = additionalEntry;
    257             }
    258 
    259             pathField.set(loader, path.toString());
    260             expandFieldArray(loader, "mPaths", extraPaths);
    261             expandFieldArray(loader, "mFiles", extraFiles);
    262         }
    263     }
    264 
    265 }
    266