Index: chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/DexLoader.java |
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/DexLoader.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/DexLoader.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bbad6365b184c83b6c222ef490939f01cf61184d |
--- /dev/null |
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/DexLoader.java |
@@ -0,0 +1,147 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.webapk.shell_apk; |
+ |
+import android.content.Context; |
+import android.util.Log; |
+ |
+import dalvik.system.BaseDexClassLoader; |
+ |
+import java.io.File; |
+import java.io.FileOutputStream; |
+import java.io.InputStream; |
+ |
+/** |
+ * Creates ClassLoader for .dex file in a remote Context's APK. |
+ */ |
+public class DexLoader { |
+ private static final int BUFFER_SIZE = 16 * 1024; |
+ private static final String TAG = "cr.DexLoader"; |
+ |
+ /** |
+ * Creates ClassLoader for .dex file in {@link remoteContext}'s APK. |
+ * @param remoteContext The context with the APK with the .dex file. |
+ * @param dexName The name of the .dex file in the APK. |
+ * @param canaryClassName Name of class in the .dex file. Used for testing the ClassLoader |
+ * before returning it. |
+ * @param remoteDexFile Location of already extracted .dex file from APK. |
+ * @param localDexDir Writable directory for caching data to speed up future calls to |
+ * {@link #load()}. |
+ * @return The ClassLoader. Returns null on an error. |
+ */ |
+ public static ClassLoader load(Context remoteContext, String dexName, String canaryClassName, |
+ File remoteDexFile, File localDexDir) { |
+ File localDexFile = new File(localDexDir, dexName); |
+ |
+ // If {@link localDexFile} exists, technique #2 was previously used to create the |
+ // ClassLoader. Skip right to the technique which worked last time. |
+ if (remoteDexFile != null && remoteDexFile.exists() && !localDexFile.exists()) { |
+ // Technique #1: At startup, Chrome code extracts the .dex file from its assets and |
+ // guesses where DexClassLoader searches for the odex by default and puts the odex |
+ // there. Try using the odex from Chrome's data directory. |
+ ClassLoader loader = tryCreatingClassLoader(canaryClassName, remoteDexFile, null); |
+ if (loader != null) { |
+ return loader; |
+ } |
+ } |
+ |
+ // Technique #2: Extract the .dex file from the remote context's APK. Create a |
+ // ClassLoader from the extracted file. |
+ if (!localDexFile.exists() || localDexFile.length() == 0) { |
+ localDexDir.mkdirs(); |
+ |
+ InputStream inputStream; |
+ try { |
+ inputStream = remoteContext.getAssets().open(dexName); |
+ } catch (Exception e) { |
+ Log.w(TAG, "Could not extract dex from assets"); |
+ e.printStackTrace(); |
+ return null; |
+ } |
+ copyFromInputStream(inputStream, localDexFile); |
+ } |
+ |
+ File localOptimizedDir = new File(localDexDir, "optimized"); |
+ localOptimizedDir.mkdirs(); |
+ |
+ return tryCreatingClassLoader(canaryClassName, localDexFile, localOptimizedDir); |
+ } |
+ |
+ /** |
+ * Deletes any files cached by {@link #load()}. |
+ * @param localDexDir Cache directory passed to {@link #load()}. |
+ */ |
+ public static void deleteCachedDexes(File localDexDir) { |
+ deleteChildren(localDexDir); |
+ } |
+ |
+ /** |
+ * Deletes all of a directory's children including subdirectories. |
+ * @param parentDir Directory whose children should be deleted. |
+ */ |
+ private static void deleteChildren(File parentDir) { |
+ if (!parentDir.isDirectory()) { |
+ return; |
+ } |
+ |
+ File[] files = parentDir.listFiles(); |
+ if (files != null) { |
+ for (File file : files) { |
+ deleteChildren(file); |
+ if (!file.delete()) { |
+ Log.e(TAG, "Could not delete " + file.getPath()); |
+ } |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Copies an InputStream to a file. |
+ * @param inputStream |
+ * @param outFile The destination file. |
+ */ |
+ private static void copyFromInputStream(InputStream inputStream, File outFile) { |
+ try (FileOutputStream outputStream = new FileOutputStream(outFile)) { |
+ byte[] buffer = new byte[BUFFER_SIZE]; |
+ int count = 0; |
+ while ((count = inputStream.read(buffer, 0, BUFFER_SIZE)) != -1) { |
+ outputStream.write(buffer, 0, count); |
+ } |
+ } catch (Exception e) { |
+ } finally { |
+ try { |
+ inputStream.close(); |
+ } catch (Exception e) { |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Tries to create ClassLoader with the given .dex file and optimized dex directory. |
+ * @param canaryClassName Name of class in the .dex file. Used for testing the ClassLoader |
+ * before returning it. |
+ * @param dexFile .dex file to create ClassLoader for. |
+ * @param optimizedDir Directory for storing the optimized dex file. If null, an OS-defined |
+ * path based on {@link dexFile} is used. |
+ * @return The ClassLoader. Returns null on an error. |
+ */ |
+ private static ClassLoader tryCreatingClassLoader( |
+ String canaryClassName, File dexFile, File optimizedDir) { |
+ try { |
+ ClassLoader loader = new BaseDexClassLoader( |
+ dexFile.getPath(), optimizedDir, null, ClassLoader.getSystemClassLoader()); |
+ // Loading {@link canaryClassName} will throw an exception if the .dex file cannot be |
+ // loaded. |
+ loader.loadClass(canaryClassName); |
+ return loader; |
+ } catch (Exception e) { |
+ String optimizedDirPath = (optimizedDir == null) ? null : optimizedDir.getPath(); |
+ Log.w(TAG, "Could not load dex from " + dexFile.getPath() + " with optimized directory " |
+ + optimizedDirPath); |
+ e.printStackTrace(); |
+ return null; |
+ } |
+ } |
+} |