MultiDex.java revision 66f379f774e06e650375750d18b1695ddb853371
1667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/* 2667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Copyright (C) 2013 The Android Open Source Project 3667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 4667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Licensed under the Apache License, Version 2.0 (the "License"); 5667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * you may not use this file except in compliance with the License. 6667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * You may obtain a copy of the License at 7667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 8667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * http://www.apache.org/licenses/LICENSE-2.0 9667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 10667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Unless required by applicable law or agreed to in writing, software 11667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * distributed under the License is distributed on an "AS IS" BASIS, 12667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * See the License for the specific language governing permissions and 14667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * limitations under the License. 15667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 16667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 17667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chupackage android.support.multidex; 18667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 1966f379f774e06e650375750d18b1695ddb853371Maurice Chuimport dalvik.system.DexFile; 2066f379f774e06e650375750d18b1695ddb853371Maurice Chu 21667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.content.Context; 22667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.content.pm.ApplicationInfo; 23667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.content.pm.PackageManager; 24667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.os.Build; 25667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.util.Log; 26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 27667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File; 28667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException; 29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.Array; 30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.Field; 31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.InvocationTargetException; 32667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.Method; 33667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList; 34667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.Arrays; 35667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.HashSet; 36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List; 37667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ListIterator; 38667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.Set; 3966f379f774e06e650375750d18b1695ddb853371Maurice Chuimport java.util.zip.ZipFile; 40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 41667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/** 42667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Monkey patches {@link Context#getClassLoader() the application context class 43667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * loader} in order to load classes from more than one dex file. The primary 44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code classes.dex} file necessary for calling this class methods. secondary 45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dex files named classes2.dex, classes".dex... found in the application apk 46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * will be added to the classloader after first call to 47667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@link #install(Context)}. 48667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 49667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * <p/> 50667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * <strong>IMPORTANT:</strong>This library provides compatibility for platforms 51667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * with API level 4 through 19. This library does nothing on newer versions of 52667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * the platform which provide built-in support for secondary dex files. 53667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 54667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chupublic final class MultiDex { 55667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 56667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu static final String TAG = "MultiDex"; 57667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 58667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String SECONDARY_FOLDER_NAME = "secondary-dexes"; 59667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 60667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final int SUPPORTED_MULTIDEX_SDK_VERSION = 20; 61667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 62667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final int MIN_SDK_VERSION = 4; 63667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 64667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final Set<String> installedApk = new HashSet<String>(); 65667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 66667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private MultiDex() {} 67667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 68667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 69667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Patches the application context class loader by appending extra dex files 70667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * loaded from the application apk. Call this method first thing in your 71667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code Application#OnCreate}, {@code Instrumentation#OnCreate}, 72667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code BackupAgent#OnCreate}, {@code Service#OnCreate}, 73667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code BroadcastReceiver#onReceive}, {@code Activity#OnCreate} and 74667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code ContentProvider#OnCreate} . 75667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 76667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param context application context. 77667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws RuntimeException if an error occurred preventing the classloader 78667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * extension. 79667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 80667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu public static void install(Context context) { 81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 82667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) { 83667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT 84667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); 85667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 86667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 87667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 88667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 89667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu PackageManager pm; 90667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String packageName; 91667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 92667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu pm = context.getPackageManager(); 93667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu packageName = context.getPackageName(); 94667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (RuntimeException e) { 95667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* Ignore those exceptions so that we don't break tests relying on Context like 96667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * a android.test.mock.MockContext or a android.content.ContextWrapper with a null 97667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * base Context. 98667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 99667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " + 100667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu "Must be running in test mode. Skip patching.", e); 101667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 102667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 103667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (pm == null || packageName == null) { 104667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // This is most likely a mock context, so just return without patching. 105667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 106667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 107667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ApplicationInfo applicationInfo = 108667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); 109667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (applicationInfo == null) { 110667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // This is from a mock context, so just return without patching. 111667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 112667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 113667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 114667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu synchronized (installedApk) { 115667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String apkPath = applicationInfo.sourceDir; 116667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (installedApk.contains(apkPath)) { 117667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 118667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 119667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu installedApk.add(apkPath); 120667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 121667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (Build.VERSION.SDK_INT >= SUPPORTED_MULTIDEX_SDK_VERSION) { 122667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // STOPSHIP: Any app that uses this class needs approval before being released 123667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // as well as figuring out what the right behavior should be here. 124667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new RuntimeException("Platform support of multidex for SDK " + 125667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Build.VERSION.SDK_INT + " has not been confirmed yet."); 126667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 127667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 128667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 129667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.BaseDexClassLoader. We modify its 130667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexPathList pathList field to append additional DEX 131667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 132667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 133667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ClassLoader loader; 134667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 135667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu loader = context.getClassLoader(); 136667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (RuntimeException e) { 137667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* Ignore those exceptions so that we don't break tests relying on Context like 138667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * a android.test.mock.MockContext or a android.content.ContextWrapper with a 139667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * null base Context. 140667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 141667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failure while trying to obtain Context class loader. " + 142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu "Must be running in test mode. Skip patching.", e); 143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (loader == null) { 146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // Note, the context class loader is null when running Robolectric tests. 147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.e(TAG, 148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu "Context class loader is null. Must be running in test mode. " 149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu + "Skip patching."); 150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 151667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 152667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File dexDir = new File(context.getFilesDir(), SECONDARY_FOLDER_NAME); 154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir); 155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!files.isEmpty()) { 156667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (Build.VERSION.SDK_INT >= 19) { 157667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu V19.install(loader, files, dexDir); 158667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } else if (Build.VERSION.SDK_INT >= 14) { 159667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu V14.install(loader, files, dexDir); 160667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } else { 161667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu V4.install(loader, files, dexDir); 162667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 163667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 164667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 165667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 166667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (Exception e) { 167667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.e(TAG, "Multidex installation failure", e); 168667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ")."); 169667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 170667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 171667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 172667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 173667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Locates a given field anywhere in the class inheritance hierarchy. 174667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 175667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param instance an object to search the field into. 176667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param name field name 177667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @return a field object 178667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws NoSuchFieldException if the field cannot be located 179667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 180667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Field findField(Object instance, String name) throws NoSuchFieldException { 181667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { 182667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 183667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field field = clazz.getDeclaredField(name); 184667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 185667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 186667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!field.isAccessible()) { 187667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu field.setAccessible(true); 188667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 189667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 190667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return field; 191667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (NoSuchFieldException e) { 192667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // ignore and search next 193667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 194667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 195667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 196667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); 197667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 198667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 199667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 200667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Locates a given method anywhere in the class inheritance hierarchy. 201667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 202667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param instance an object to search the method into. 203667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param name method name 204667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param parameterTypes method parameter types 205667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @return a method object 206667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws NoSuchMethodException if the method cannot be located 207667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 208667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Method findMethod(Object instance, String name, Class<?>... parameterTypes) 209667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws NoSuchMethodException { 210667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { 211667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 212667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Method method = clazz.getDeclaredMethod(name, parameterTypes); 213667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 214667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 215667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!method.isAccessible()) { 216667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu method.setAccessible(true); 217667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 218667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 219667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return method; 220667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (NoSuchMethodException e) { 221667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // ignore and search next 222667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 223667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 224667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 225667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new NoSuchMethodException("Method " + name + " with parameters " + 226667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Arrays.asList(parameterTypes) + " not found in " + instance.getClass()); 227667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 228667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 229667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 230667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Replace the value of a field containing a non null array, by a new array containing the 231667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * elements of the original array plus the elements of extraElements. 232667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param instance the instance whose field is to be modified. 233667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param fieldName the field to modify. 234667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param extraElements elements to append at the end of the array. 235667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 236667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void expandFieldArray(Object instance, String fieldName, 237667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, 238667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu IllegalAccessException { 239667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field jlrField = findField(instance, fieldName); 240667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object[] original = (Object[]) jlrField.get(instance); 241667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object[] combined = (Object[]) Array.newInstance( 242667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu original.getClass().getComponentType(), original.length + extraElements.length); 243667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu System.arraycopy(original, 0, combined, 0, original.length); 244667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); 245667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu jlrField.set(instance, combined); 246667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 247667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 248667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 249667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Installer for platform versions 19. 250667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 251667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final class V19 { 252667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 253667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void install(ClassLoader loader, List<File> additionalClassPathEntries, 254667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File optimizedDirectory) 255667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalArgumentException, IllegalAccessException, 256667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchFieldException, InvocationTargetException, NoSuchMethodException { 257667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 258667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.BaseDexClassLoader. We modify its 259667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexPathList pathList field to append additional DEX 260667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 261667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 262667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field pathListField = findField(loader, "pathList"); 263667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList = pathListField.get(loader); 264667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 265667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, 266667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new ArrayList<File>(additionalClassPathEntries), optimizedDirectory, 267667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions)); 268667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (suppressedExceptions.size() > 0) { 269667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field suppressedExceptionsField = 270667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu findField(loader, "dexElementsSuppressedExceptions"); 271667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu IOException[] dexElementsSuppressedExceptions = 272667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu (IOException[]) suppressedExceptionsField.get(loader); 273667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 274667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (dexElementsSuppressedExceptions == null) { 275667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexElementsSuppressedExceptions = 276667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions.toArray( 277667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new IOException[suppressedExceptions.size()]); 278667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } else { 279667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu IOException[] combined = 280667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new IOException[suppressedExceptions.size() + 281667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexElementsSuppressedExceptions.length]; 282667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions.toArray(combined); 283667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu System.arraycopy(dexElementsSuppressedExceptions, 0, combined, 284667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions.size(), dexElementsSuppressedExceptions.length); 285667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexElementsSuppressedExceptions = combined; 286667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 287667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 288667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptionsField.set(loader, dexElementsSuppressedExceptions); 289667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 290667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 291667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 292667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 293667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * A wrapper around 294667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code private static final dalvik.system.DexPathList#makeDexElements}. 295667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 296667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Object[] makeDexElements( 297667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList, ArrayList<File> files, File optimizedDirectory, 298667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ArrayList<IOException> suppressedExceptions) 299667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalAccessException, InvocationTargetException, 300667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchMethodException { 301667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Method makeDexElements = 302667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, 303667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ArrayList.class); 304667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 305667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, 306667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions); 307667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 308667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 309667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 310667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 311667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Installer for platform versions 14, 15, 16, 17 and 18. 312667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 313667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final class V14 { 314667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 315667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void install(ClassLoader loader, List<File> additionalClassPathEntries, 316667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File optimizedDirectory) 317667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalArgumentException, IllegalAccessException, 318667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchFieldException, InvocationTargetException, NoSuchMethodException { 319667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 320667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.BaseDexClassLoader. We modify its 321667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexPathList pathList field to append additional DEX 322667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 323667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 324667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field pathListField = findField(loader, "pathList"); 325667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList = pathListField.get(loader); 326667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, 327667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new ArrayList<File>(additionalClassPathEntries), optimizedDirectory)); 328667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 329667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 330667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 331667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * A wrapper around 332667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code private static final dalvik.system.DexPathList#makeDexElements}. 333667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 334667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Object[] makeDexElements( 335667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList, ArrayList<File> files, File optimizedDirectory) 336667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalAccessException, InvocationTargetException, 337667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchMethodException { 338667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Method makeDexElements = 339667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); 340667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 341667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); 342667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 343667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 344667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 345667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 346667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Installer for platform versions 4 to 13. 347667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 348667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final class V4 { 349667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void install(ClassLoader loader, List<File> additionalClassPathEntries, 350667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File optimizedDirectory) 351667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalArgumentException, IllegalAccessException, 352667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchFieldException, IOException { 353667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 354667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexClassLoader. We modify its 355667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexPathList pathList field to append additional DEX 356667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 357667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 358667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int extraSize = additionalClassPathEntries.size(); 359667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 360667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field pathField = findField(loader, "path"); 361667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 362667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu StringBuilder path = new StringBuilder((String) pathField.get(loader)); 363667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String[] extraPaths = new String[extraSize]; 364667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File[] extraFiles = new File[extraSize]; 36566f379f774e06e650375750d18b1695ddb853371Maurice Chu ZipFile[] extraZips = new ZipFile[extraSize]; 366667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu DexFile[] extraDexs = new DexFile[extraSize]; 367667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (ListIterator<File> iterator = additionalClassPathEntries.listIterator(); 368667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu iterator.hasNext();) { 369667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File additionalEntry = iterator.next(); 370667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String entryPath = additionalEntry.getAbsolutePath(); 371667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu path.append(':').append(entryPath); 372667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int index = iterator.previousIndex(); 373667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extraPaths[index] = entryPath; 374667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extraFiles[index] = additionalEntry; 37566f379f774e06e650375750d18b1695ddb853371Maurice Chu extraZips[index] = new ZipFile(additionalEntry); 376667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0); 377667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 378667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 379667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu pathField.set(loader, path.toString()); 380667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(loader, "mPaths", extraPaths); 381667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(loader, "mFiles", extraFiles); 38266f379f774e06e650375750d18b1695ddb853371Maurice Chu expandFieldArray(loader, "mZips", extraZips); 383667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(loader, "mDexs", extraDexs); 384667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 385667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 386667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 387667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu} 388