summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Johnson <aqj@google.com>2016-07-26 09:17:41 +0000
committerandroid-build-merger <android-build-merger@google.com>2016-07-26 09:17:41 +0000
commitc094a2de01995fbed174287e2c9ad0c0415f5ece (patch)
tree0ffa33a56c249df4c64e94f5cd3240d1056dcd35
parent9ee9a9bd9d8fa602d01567e5c7f00f107ce81019 (diff)
parentd89431ead31219eccb6f261978932a0de3c537d8 (diff)
downloadmultidex-nougat-mr1-volantis-release.tar.gz
Prevent concurrent extractionsandroid-cts_7.1_r1android-cts-7.1_r9android-cts-7.1_r8android-cts-7.1_r7android-cts-7.1_r6android-cts-7.1_r5android-cts-7.1_r4android-cts-7.1_r3android-cts-7.1_r29android-cts-7.1_r28android-cts-7.1_r27android-cts-7.1_r26android-cts-7.1_r25android-cts-7.1_r24android-cts-7.1_r23android-cts-7.1_r22android-cts-7.1_r21android-cts-7.1_r20android-cts-7.1_r2android-cts-7.1_r19android-cts-7.1_r18android-cts-7.1_r17android-cts-7.1_r16android-cts-7.1_r15android-cts-7.1_r14android-cts-7.1_r13android-cts-7.1_r12android-cts-7.1_r11android-cts-7.1_r10android-cts-7.1_r1android-7.1.2_r9android-7.1.2_r8android-7.1.2_r6android-7.1.2_r5android-7.1.2_r4android-7.1.2_r39android-7.1.2_r38android-7.1.2_r37android-7.1.2_r36android-7.1.2_r33android-7.1.2_r32android-7.1.2_r30android-7.1.2_r3android-7.1.2_r29android-7.1.2_r28android-7.1.2_r27android-7.1.2_r25android-7.1.2_r24android-7.1.2_r23android-7.1.2_r2android-7.1.2_r19android-7.1.2_r18android-7.1.2_r17android-7.1.2_r16android-7.1.2_r15android-7.1.2_r14android-7.1.2_r13android-7.1.2_r12android-7.1.2_r11android-7.1.2_r10android-7.1.2_r1android-7.1.1_r9android-7.1.1_r8android-7.1.1_r7android-7.1.1_r61android-7.1.1_r60android-7.1.1_r6android-7.1.1_r59android-7.1.1_r58android-7.1.1_r57android-7.1.1_r56android-7.1.1_r55android-7.1.1_r54android-7.1.1_r53android-7.1.1_r52android-7.1.1_r51android-7.1.1_r50android-7.1.1_r49android-7.1.1_r48android-7.1.1_r47android-7.1.1_r46android-7.1.1_r45android-7.1.1_r44android-7.1.1_r43android-7.1.1_r42android-7.1.1_r41android-7.1.1_r40android-7.1.1_r4android-7.1.1_r39android-7.1.1_r38android-7.1.1_r35android-7.1.1_r33android-7.1.1_r32android-7.1.1_r31android-7.1.1_r3android-7.1.1_r28android-7.1.1_r27android-7.1.1_r26android-7.1.1_r25android-7.1.1_r24android-7.1.1_r23android-7.1.1_r22android-7.1.1_r21android-7.1.1_r20android-7.1.1_r2android-7.1.1_r17android-7.1.1_r16android-7.1.1_r15android-7.1.1_r14android-7.1.1_r13android-7.1.1_r12android-7.1.1_r11android-7.1.1_r10android-7.1.1_r1android-7.1.0_r7android-7.1.0_r6android-7.1.0_r5android-7.1.0_r4android-7.1.0_r3android-7.1.0_r2android-7.1.0_r1nougat-mr2.3-releasenougat-mr2.2-releasenougat-mr2.1-releasenougat-mr2-security-releasenougat-mr2-releasenougat-mr2-pixel-releasenougat-mr2-devnougat-mr1.8-releasenougat-mr1.7-releasenougat-mr1.6-releasenougat-mr1.5-releasenougat-mr1.4-releasenougat-mr1.3-releasenougat-mr1.2-releasenougat-mr1.1-releasenougat-mr1-volantis-releasenougat-mr1-security-releasenougat-mr1-releasenougat-mr1-flounder-releasenougat-mr1-devnougat-mr1-cts-releasenougat-dr1-release
am: d89431ead3 Change-Id: I8fe1f1aeb0d814aca58dbc9d35089377d8af0a9a
-rw-r--r--library/src/android/support/multidex/MultiDexExtractor.java123
1 files changed, 75 insertions, 48 deletions
diff --git a/library/src/android/support/multidex/MultiDexExtractor.java b/library/src/android/support/multidex/MultiDexExtractor.java
index 4aef9f2..32d7ee9 100644
--- a/library/src/android/support/multidex/MultiDexExtractor.java
+++ b/library/src/android/support/multidex/MultiDexExtractor.java
@@ -30,8 +30,9 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
@@ -70,12 +71,14 @@ final class MultiDexExtractor {
/* Keep value away from 0 because it is a too probable time stamp value */
private static final long NO_VALUE = -1L;
+ private static final String LOCK_FILENAME = "MultiDex.lock";
+
/**
* Extracts application secondary dexes into files in the application data
* directory.
*
* @return a list of files that were created. The list may be empty if there
- * are no secondary dex files.
+ * are no secondary dex files. Never return null.
* @throws IOException if encounters a problem while reading or writing
* secondary dex files
*/
@@ -86,27 +89,64 @@ final class MultiDexExtractor {
long currentCrc = getZipCrc(sourceApk);
+ // Validity check and extraction must be done only while the lock file has been taken.
+ File lockFile = new File(dexDir, LOCK_FILENAME);
+ RandomAccessFile lockRaf = new RandomAccessFile(lockFile, "rw");
+ FileChannel lockChannel = null;
+ FileLock cacheLock = null;
List<File> files;
- if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
- try {
- files = loadExistingExtractions(context, sourceApk, dexDir);
- } catch (IOException ioe) {
- Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
- + " falling back to fresh extraction", ioe);
+ IOException releaseLockException = null;
+ try {
+ lockChannel = lockRaf.getChannel();
+ Log.i(TAG, "Blocking on lock " + lockFile.getPath());
+ cacheLock = lockChannel.lock();
+ Log.i(TAG, lockFile.getPath() + " locked");
+
+ if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
+ try {
+ files = loadExistingExtractions(context, sourceApk, dexDir);
+ } 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.size() + 1);
+
+ }
+ } else {
+ Log.i(TAG, "Detected that extraction must be performed.");
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
-
}
- } else {
- Log.i(TAG, "Detected that extraction must be performed.");
- files = performExtractions(sourceApk, dexDir);
- putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
+ } finally {
+ if (cacheLock != null) {
+ try {
+ cacheLock.release();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to release lock on " + lockFile.getPath());
+ // Exception while releasing the lock is bad, we want to report it, but not at
+ // the price of overriding any already pending exception.
+ releaseLockException = e;
+ }
+ }
+ if (lockChannel != null) {
+ closeQuietly(lockChannel);
+ }
+ closeQuietly(lockRaf);
+ }
+
+ if (releaseLockException != null) {
+ throw releaseLockException;
}
Log.i(TAG, "load found " + files.size() + " secondary dex files");
return files;
}
+ /**
+ * Load previously extracted secondary dex files. Should be called only while owning the lock on
+ * {@link #LOCK_FILENAME}.
+ */
private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir)
throws IOException {
Log.i(TAG, "loading existing secondary dex files");
@@ -133,6 +173,11 @@ final class MultiDexExtractor {
return files;
}
+
+ /**
+ * 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) {
SharedPreferences prefs = getMultiDexPreferences(context);
return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive))
@@ -227,20 +272,27 @@ final class MultiDexExtractor {
return files;
}
+ /**
+ * 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,
int totalDexNumber) {
SharedPreferences prefs = getMultiDexPreferences(context);
SharedPreferences.Editor edit = prefs.edit();
edit.putLong(KEY_TIME_STAMP, timeStamp);
edit.putLong(KEY_CRC, crc);
- /* SharedPreferences.Editor doc says that apply() and commit() "atomically performs the
- * requested modifications" it should be OK to rely on saving the dex files number (getting
- * old number value would go along with old crc and time stamp).
- */
edit.putInt(KEY_DEX_NUMBER, totalDexNumber);
- apply(edit);
+ /* Use commit() and not apply() as advised by the doc because we need synchronous writing of
+ * the editor content and apply is doing an "asynchronous commit to disk".
+ */
+ edit.commit();
}
+ /**
+ * Get the MuliDex {@link SharedPreferences} for the current application. Should be called only
+ * while owning the lock on {@link #LOCK_FILENAME}.
+ */
private static SharedPreferences getMultiDexPreferences(Context context) {
return context.getSharedPreferences(PREFS_FILE,
Build.VERSION.SDK_INT < 11 /* Build.VERSION_CODES.HONEYCOMB */
@@ -249,15 +301,16 @@ final class MultiDexExtractor {
}
/**
- * This removes any files that do not have the correct prefix.
+ * This removes old files.
*/
private static void prepareDexDir(File dexDir, final String extractedFilePrefix) {
- // Clean possible old files
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
- return !pathname.getName().startsWith(extractedFilePrefix);
+ String name = pathname.getName();
+ return !(name.startsWith(extractedFilePrefix)
+ || name.equals(LOCK_FILENAME));
}
};
File[] files = dexDir.listFiles(filter);
@@ -343,30 +396,4 @@ final class MultiDexExtractor {
Log.w(TAG, "Failed to close resource", e);
}
}
-
- // The following is taken from SharedPreferencesCompat to avoid having a dependency of the
- // multidex support library on another support library.
- private static Method sApplyMethod; // final
- static {
- try {
- Class<?> cls = SharedPreferences.Editor.class;
- sApplyMethod = cls.getMethod("apply");
- } catch (NoSuchMethodException unused) {
- sApplyMethod = null;
- }
- }
-
- private static void apply(SharedPreferences.Editor editor) {
- if (sApplyMethod != null) {
- try {
- sApplyMethod.invoke(editor);
- return;
- } catch (InvocationTargetException unused) {
- // fall through
- } catch (IllegalAccessException unused) {
- // fall through
- }
- }
- editor.commit();
- }
}