diff options
author | Yohann Roussel <yroussel@google.com> | 2017-06-01 09:49:19 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-06-01 09:49:19 +0000 |
commit | 78a8310adeb32c38513e2c10711c06a14e0825c6 (patch) | |
tree | dda41769034d1f487915fd4171c13501019ec3cb /library | |
parent | 4f494eea21c9d434673052c9e54f551a7fc977a8 (diff) | |
parent | d14d4b8cbdc56dd5b8b6cae0b76c1303953339c1 (diff) | |
download | multidex-78a8310adeb32c38513e2c10711c06a14e0825c6.tar.gz |
Allow multidex of instrumentations am: 50823410c9 am: 53329c37a1
am: d14d4b8cbd
Change-Id: Iea5cf46d51bfee031ab6eafebf918b6aafcf0016
Diffstat (limited to 'library')
-rw-r--r-- | library/src/android/support/multidex/MultiDex.java | 204 | ||||
-rw-r--r-- | library/src/android/support/multidex/MultiDexExtractor.java | 54 |
2 files changed, 173 insertions, 85 deletions
diff --git a/library/src/android/support/multidex/MultiDex.java b/library/src/android/support/multidex/MultiDex.java index d35da96..ab7f668 100644 --- a/library/src/android/support/multidex/MultiDex.java +++ b/library/src/android/support/multidex/MultiDex.java @@ -17,6 +17,7 @@ package android.support.multidex; import android.app.Application; +import android.app.Instrumentation; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Build; @@ -70,7 +71,9 @@ public final class MultiDex { private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1; - private static final Set<String> installedApk = new HashSet<String>(); + private static final String NO_KEY_PREFIX = ""; + + private static final Set<File> installedApk = new HashSet<File>(); private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version")); @@ -88,83 +91,164 @@ public final class MultiDex { * extension. */ public static void install(Context context) { - Log.i(TAG, "install"); + Log.i(TAG, "Installing application"); if (IS_VM_MULTIDEX_CAPABLE) { Log.i(TAG, "VM has multidex support, MultiDex support library is disabled."); return; } if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) { - throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT + throw new RuntimeException("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); } try { ApplicationInfo applicationInfo = getApplicationInfo(context); if (applicationInfo == null) { - // Looks like running on a test Context, so just return without patching. - return; + Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:" + + " MultiDex support library is disabled."); + return; } - synchronized (installedApk) { - String apkPath = applicationInfo.sourceDir; - if (installedApk.contains(apkPath)) { - return; - } - installedApk.add(apkPath); - - if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) { - Log.w(TAG, "MultiDex is not guaranteed to work in SDK version " - + Build.VERSION.SDK_INT + ": SDK version higher than " - + MAX_SUPPORTED_SDK_VERSION + " should be backed by " - + "runtime with built-in multidex capabilty but it's not the " - + "case here: java.vm.version=\"" - + System.getProperty("java.vm.version") + "\""); - } + doInstallation(context, + new File(applicationInfo.sourceDir), + new File(applicationInfo.dataDir), + CODE_CACHE_SECONDARY_FOLDER_NAME, + NO_KEY_PREFIX); - /* The patched class loader is expected to be a descendant of - * dalvik.system.BaseDexClassLoader. We modify its - * dalvik.system.DexPathList pathList field to append additional DEX - * file entries. - */ - ClassLoader loader; - try { - loader = context.getClassLoader(); - } catch (RuntimeException e) { - /* Ignore those exceptions so that we don't break tests relying on Context like - * a android.test.mock.MockContext or a android.content.ContextWrapper with a - * null base Context. - */ - Log.w(TAG, "Failure while trying to obtain Context class loader. " + - "Must be running in test mode. Skip patching.", e); - return; - } - if (loader == null) { - // Note, the context class loader is null when running Robolectric tests. - Log.e(TAG, - "Context class loader is null. Must be running in test mode. " - + "Skip patching."); - return; - } + } catch (Exception e) { + Log.e(TAG, "MultiDex installation failure", e); + throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ")."); + } + Log.i(TAG, "install done"); + } - try { - clearOldDexDir(context); - } catch (Throwable t) { - Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, " - + "continuing without cleaning.", t); - } + /** + * Patches the instrumentation context class loader by appending extra dex files + * loaded from the instrumentation apk and the application apk. This method should be called in + * the onCreate of your {@link Instrumentation}, see + * {@link com.android.test.runner.MultiDexTestRunner} for an example. + * + * @param instrumentationContext instrumentation context. + * @param targetContext target application context. + * @throws RuntimeException if an error occurred preventing the classloader + * extension. + */ + public static void installInstrumentation(Context instrumentationContext, + Context targetContext) { + Log.i(TAG, "Installing instrumentation"); + + if (IS_VM_MULTIDEX_CAPABLE) { + Log.i(TAG, "VM has multidex support, MultiDex support library is disabled."); + return; + } + + if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) { + throw new RuntimeException("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT + + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); + } + try { + + ApplicationInfo instrumentationInfo = getApplicationInfo(instrumentationContext); + if (instrumentationInfo == null) { + Log.i(TAG, "No ApplicationInfo available for instrumentation, i.e. running on a" + + " test Context: MultiDex support library is disabled."); + return; + } - File dexDir = getDexDir(context, applicationInfo); - List<? extends File> files = - MultiDexExtractor.load(context, applicationInfo, dexDir, false); - installSecondaryDexes(loader, dexDir, files); + ApplicationInfo applicationInfo = getApplicationInfo(targetContext); + if (applicationInfo == null) { + Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:" + + " MultiDex support library is disabled."); + return; } + String instrumentationPrefix = instrumentationContext.getPackageName() + "."; + + File dataDir = new File(applicationInfo.dataDir); + + doInstallation(targetContext, + new File(instrumentationInfo.sourceDir), + dataDir, + instrumentationPrefix + CODE_CACHE_SECONDARY_FOLDER_NAME, + instrumentationPrefix); + + doInstallation(targetContext, + new File(applicationInfo.sourceDir), + dataDir, + CODE_CACHE_SECONDARY_FOLDER_NAME, + NO_KEY_PREFIX); } catch (Exception e) { - Log.e(TAG, "Multidex installation failure", e); - throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ")."); + Log.e(TAG, "MultiDex installation failure", e); + throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ")."); + } + Log.i(TAG, "Installation done"); + } + + /** + * @param mainContext context used to get filesDir, to save preference and to get the + * classloader to patch. + * @param sourceApk Apk file. + * @param dataDir data directory to use for code cache simulation. + * @param secondaryFolderName name of the folder for storing extractions. + * @param prefsKeyPrefix prefix of all stored preference keys. + */ + private static void doInstallation(Context mainContext, File sourceApk, File dataDir, + String secondaryFolderName, String prefsKeyPrefix) throws IOException, + IllegalArgumentException, IllegalAccessException, NoSuchFieldException, + InvocationTargetException, NoSuchMethodException { + synchronized (installedApk) { + if (installedApk.contains(sourceApk)) { + return; + } + installedApk.add(sourceApk); + + if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) { + Log.w(TAG, "MultiDex is not guaranteed to work in SDK version " + + Build.VERSION.SDK_INT + ": SDK version higher than " + + MAX_SUPPORTED_SDK_VERSION + " should be backed by " + + "runtime with built-in multidex capabilty but it's not the " + + "case here: java.vm.version=\"" + + System.getProperty("java.vm.version") + "\""); + } + + /* The patched class loader is expected to be a descendant of + * dalvik.system.BaseDexClassLoader. We modify its + * dalvik.system.DexPathList pathList field to append additional DEX + * file entries. + */ + ClassLoader loader; + try { + loader = mainContext.getClassLoader(); + } catch (RuntimeException e) { + /* Ignore those exceptions so that we don't break tests relying on Context like + * a android.test.mock.MockContext or a android.content.ContextWrapper with a + * null base Context. + */ + Log.w(TAG, "Failure while trying to obtain Context class loader. " + + "Must be running in test mode. Skip patching.", e); + return; + } + if (loader == null) { + // Note, the context class loader is null when running Robolectric tests. + Log.e(TAG, + "Context class loader is null. Must be running in test mode. " + + "Skip patching."); + return; + } + + try { + clearOldDexDir(mainContext); + } catch (Throwable t) { + Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, " + + "continuing without cleaning.", t); + } + + File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName); + List<? extends File> files = + MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false); + installSecondaryDexes(loader, dexDir, files); } - Log.i(TAG, "install done"); } private static ApplicationInfo getApplicationInfo(Context context) { @@ -335,9 +419,9 @@ public final class MultiDex { } } - private static File getDexDir(Context context, ApplicationInfo applicationInfo) + private static File getDexDir(Context context, File dataDir, String secondaryFolderName) throws IOException { - File cache = new File(applicationInfo.dataDir, CODE_CACHE_NAME); + File cache = new File(dataDir, CODE_CACHE_NAME); try { mkdirChecked(cache); } catch (IOException e) { @@ -348,7 +432,7 @@ public final class MultiDex { cache = new File(context.getFilesDir(), CODE_CACHE_NAME); mkdirChecked(cache); } - File dexDir = new File(cache, CODE_CACHE_SECONDARY_FOLDER_NAME); + File dexDir = new File(cache, secondaryFolderName); mkdirChecked(dexDir); return dexDir; } diff --git a/library/src/android/support/multidex/MultiDexExtractor.java b/library/src/android/support/multidex/MultiDexExtractor.java index 2d7402a..39b6bf7 100644 --- a/library/src/android/support/multidex/MultiDexExtractor.java +++ b/library/src/android/support/multidex/MultiDexExtractor.java @@ -18,7 +18,6 @@ package android.support.multidex; import android.content.Context; import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; import android.os.Build; import android.util.Log; import java.io.BufferedOutputStream; @@ -93,10 +92,11 @@ final class MultiDexExtractor { * @throws IOException if encounters a problem while reading or writing * secondary dex files */ - static List<? extends File> load(Context context, ApplicationInfo applicationInfo, File dexDir, + static List<? extends File> load(Context context, File sourceApk, File dexDir, + String prefsKeyPrefix, boolean forceReload) throws IOException { - Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")"); - final File sourceApk = new File(applicationInfo.sourceDir); + Log.i(TAG, "MultiDexExtractor.load(" + sourceApk.getPath() + ", " + forceReload + ", " + + prefsKeyPrefix + ")"); long currentCrc = getZipCrc(sourceApk); @@ -113,19 +113,21 @@ final class MultiDexExtractor { cacheLock = lockChannel.lock(); Log.i(TAG, lockFile.getPath() + " locked"); - if (!forceReload && !isModified(context, sourceApk, currentCrc)) { + if (!forceReload && !isModified(context, sourceApk, currentCrc, prefsKeyPrefix)) { try { - files = loadExistingExtractions(context, sourceApk, dexDir); + files = loadExistingExtractions(context, sourceApk, dexDir, prefsKeyPrefix); } catch (IOException ioe) { Log.w(TAG, "Failed to reload existing extracted secondary dex files," + " falling back to fresh extraction", ioe); files = performExtractions(sourceApk, dexDir); - putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files); + putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, + files); } } else { Log.i(TAG, "Detected that extraction must be performed."); files = performExtractions(sourceApk, dexDir); - putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files); + putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, + files); } } finally { if (cacheLock != null) { @@ -157,13 +159,14 @@ final class MultiDexExtractor { * {@link #LOCK_FILENAME}. */ private static List<ExtractedDex> loadExistingExtractions( - Context context, File sourceApk, File dexDir) + Context context, File sourceApk, File dexDir, + String prefsKeyPrefix) throws IOException { Log.i(TAG, "loading existing secondary dex files"); final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; SharedPreferences multiDexPreferences = getMultiDexPreferences(context); - int totalDexNumber = multiDexPreferences.getInt(KEY_DEX_NUMBER, 1); + int totalDexNumber = multiDexPreferences.getInt(prefsKeyPrefix + KEY_DEX_NUMBER, 1); final List<ExtractedDex> files = new ArrayList<ExtractedDex>(totalDexNumber - 1); for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { @@ -171,15 +174,15 @@ final class MultiDexExtractor { ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName); if (extractedFile.isFile()) { extractedFile.crc = getZipCrc(extractedFile); - long expectedCrc = - multiDexPreferences.getLong(KEY_DEX_CRC + secondaryNumber, NO_VALUE); - long expectedModTime = - multiDexPreferences.getLong(KEY_DEX_TIME + secondaryNumber, NO_VALUE); + long expectedCrc = multiDexPreferences.getLong( + prefsKeyPrefix + KEY_DEX_CRC + secondaryNumber, NO_VALUE); + long expectedModTime = multiDexPreferences.getLong( + prefsKeyPrefix + KEY_DEX_TIME + secondaryNumber, NO_VALUE); long lastModified = extractedFile.lastModified(); if ((expectedModTime != lastModified) || (expectedCrc != extractedFile.crc)) { throw new IOException("Invalid extracted dex: " + extractedFile + - ", expected modification time: " + " (key \"" + prefsKeyPrefix + "\"), expected modification time: " + expectedModTime + ", modification time: " + lastModified + ", expected crc: " + expectedCrc + ", file crc: " + extractedFile.crc); @@ -199,10 +202,11 @@ final class MultiDexExtractor { * Compare current archive and crc with values stored in {@link SharedPreferences}. Should be * called only while owning the lock on {@link #LOCK_FILENAME}. */ - private static boolean isModified(Context context, File archive, long currentCrc) { + private static boolean isModified(Context context, File archive, long currentCrc, + String prefsKeyPrefix) { SharedPreferences prefs = getMultiDexPreferences(context); - return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive)) - || (prefs.getLong(KEY_CRC, NO_VALUE) != currentCrc); + return (prefs.getLong(prefsKeyPrefix + KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive)) + || (prefs.getLong(prefsKeyPrefix + KEY_CRC, NO_VALUE) != currentCrc); } private static long getTimeStamp(File archive) { @@ -303,18 +307,18 @@ final class MultiDexExtractor { * Save {@link SharedPreferences}. Should be called only while owning the lock on * {@link #LOCK_FILENAME}. */ - private static void putStoredApkInfo(Context context, long timeStamp, long crc, - List<ExtractedDex> extractedDexes) { + private static void putStoredApkInfo(Context context, String keyPrefix, long timeStamp, + long crc, List<ExtractedDex> extractedDexes) { SharedPreferences prefs = getMultiDexPreferences(context); SharedPreferences.Editor edit = prefs.edit(); - edit.putLong(KEY_TIME_STAMP, timeStamp); - edit.putLong(KEY_CRC, crc); - edit.putInt(KEY_DEX_NUMBER, extractedDexes.size() + 1); + edit.putLong(keyPrefix + KEY_TIME_STAMP, timeStamp); + edit.putLong(keyPrefix + KEY_CRC, crc); + edit.putInt(keyPrefix + KEY_DEX_NUMBER, extractedDexes.size() + 1); int extractedDexId = 2; for (ExtractedDex dex : extractedDexes) { - edit.putLong(KEY_DEX_CRC + extractedDexId, dex.crc); - edit.putLong(KEY_DEX_TIME + extractedDexId, dex.lastModified()); + edit.putLong(keyPrefix + KEY_DEX_CRC + extractedDexId, dex.crc); + edit.putLong(keyPrefix + KEY_DEX_TIME + extractedDexId, dex.lastModified()); extractedDexId++; } /* Use commit() and not apply() as advised by the doc because we need synchronous writing of |