summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Johnson <aqj@google.com>2016-03-09 10:26:37 -0500
committerYohann Roussel <yroussel@google.com>2016-07-25 08:56:36 +0000
commitd89431ead31219eccb6f261978932a0de3c537d8 (patch)
tree76a3487bb169efad7fb3e9a46291b6c77ceeb0b8
parent1247e62e916098962f95dcba3b49d84e5ace380b (diff)
downloadmultidex-d89431ead31219eccb6f261978932a0de3c537d8.tar.gz
Prevent concurrent extractions
This prevernt multiple processes of the same application from simultaneously caching the same secondary dex files. Bug: 27263431 (cherry picked from commit 048fbf7ccf53782a265c277df38c273d43e5450e) Change-Id: Iacd9cb1ab2084a66285f120fb313b578a2f5e14b
-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();
- }
}