OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.webapk.shell_apk; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.util.Log; |
| 9 |
| 10 import dalvik.system.BaseDexClassLoader; |
| 11 |
| 12 import java.io.File; |
| 13 import java.io.FileOutputStream; |
| 14 import java.io.IOException; |
| 15 import java.io.InputStream; |
| 16 |
| 17 /** |
| 18 * Creates ClassLoader for .dex file in a remote Context's APK. |
| 19 */ |
| 20 public class DexLoader { |
| 21 private static final int BUFFER_SIZE = 16 * 1024; |
| 22 private static final String TAG = "cr.DexLoader"; |
| 23 |
| 24 /** |
| 25 * Creates ClassLoader for .dex file in {@link remoteContext}'s APK. |
| 26 * @param remoteContext The context with the APK with the .dex file. |
| 27 * @param dexName The name of the .dex file in the APK. |
| 28 * @param canaryClassName Name of class in the .dex file. Used for testing t
he ClassLoader |
| 29 * before returning it. |
| 30 * @param remoteDexFile Location of already extracted .dex file from APK. |
| 31 * @param localDexDir Writable directory for caching data to speed up future
calls to |
| 32 * {@link #load()}. |
| 33 * @return The ClassLoader. Returns null on an error. |
| 34 */ |
| 35 public static ClassLoader load(Context remoteContext, String dexName, String
canaryClassName, |
| 36 File remoteDexFile, File localDexDir) { |
| 37 File localDexFile = new File(localDexDir, dexName); |
| 38 |
| 39 // If {@link localDexFile} exists, technique #2 was previously used to c
reate the |
| 40 // ClassLoader. Skip right to the technique which worked last time. |
| 41 if (remoteDexFile != null && remoteDexFile.exists() && !localDexFile.exi
sts()) { |
| 42 // Technique #1: At startup, Chrome code extracts the .dex file from
its assets and |
| 43 // guesses where DexClassLoader searches for the odex by default and
puts the odex |
| 44 // there. Try using the odex from Chrome's data directory. |
| 45 ClassLoader loader = tryCreatingClassLoader(canaryClassName, remoteD
exFile, null); |
| 46 if (loader != null) { |
| 47 return loader; |
| 48 } |
| 49 } |
| 50 |
| 51 // Technique #2: Extract the .dex file from the remote context's APK. Cr
eate a |
| 52 // ClassLoader from the extracted file. |
| 53 if (!localDexFile.exists() || localDexFile.length() == 0) { |
| 54 if (!localDexDir.exists() && !localDexDir.mkdirs()) { |
| 55 return null; |
| 56 } |
| 57 |
| 58 InputStream inputStream; |
| 59 try { |
| 60 inputStream = remoteContext.getAssets().open(dexName); |
| 61 } catch (Exception e) { |
| 62 Log.w(TAG, "Could not extract dex from assets"); |
| 63 e.printStackTrace(); |
| 64 return null; |
| 65 } |
| 66 copyFromInputStream(inputStream, localDexFile); |
| 67 } |
| 68 |
| 69 File localOptimizedDir = new File(localDexDir, "optimized"); |
| 70 if (!localOptimizedDir.exists() && !localOptimizedDir.mkdirs()) { |
| 71 return null; |
| 72 } |
| 73 |
| 74 return tryCreatingClassLoader(canaryClassName, localDexFile, localOptimi
zedDir); |
| 75 } |
| 76 |
| 77 /** |
| 78 * Deletes any files cached by {@link #load()}. |
| 79 * @param localDexDir Cache directory passed to {@link #load()}. |
| 80 */ |
| 81 public static void deleteCachedDexes(File localDexDir) { |
| 82 deleteChildren(localDexDir); |
| 83 } |
| 84 |
| 85 /** |
| 86 * Deletes all of a directory's children including subdirectories. |
| 87 * @param parentDir Directory whose children should be deleted. |
| 88 */ |
| 89 private static void deleteChildren(File parentDir) { |
| 90 if (!parentDir.isDirectory()) { |
| 91 return; |
| 92 } |
| 93 |
| 94 File[] files = parentDir.listFiles(); |
| 95 if (files != null) { |
| 96 for (File file : files) { |
| 97 deleteChildren(file); |
| 98 if (!file.delete()) { |
| 99 Log.e(TAG, "Could not delete " + file.getPath()); |
| 100 } |
| 101 } |
| 102 } |
| 103 } |
| 104 |
| 105 /** |
| 106 * Copies an InputStream to a file. |
| 107 * @param inputStream |
| 108 * @param outFile The destination file. |
| 109 */ |
| 110 private static void copyFromInputStream(InputStream inputStream, File outFil
e) { |
| 111 try (FileOutputStream outputStream = new FileOutputStream(outFile)) { |
| 112 byte[] buffer = new byte[BUFFER_SIZE]; |
| 113 int count = 0; |
| 114 while ((count = inputStream.read(buffer, 0, BUFFER_SIZE)) != -1) { |
| 115 outputStream.write(buffer, 0, count); |
| 116 } |
| 117 } catch (IOException e) { |
| 118 } finally { |
| 119 try { |
| 120 inputStream.close(); |
| 121 } catch (Exception e) { |
| 122 } |
| 123 } |
| 124 } |
| 125 |
| 126 /** |
| 127 * Tries to create ClassLoader with the given .dex file and optimized dex di
rectory. |
| 128 * @param canaryClassName Name of class in the .dex file. Used for testing t
he ClassLoader |
| 129 * before returning it. |
| 130 * @param dexFile .dex file to create ClassLoader for. |
| 131 * @param optimizedDir Directory for storing the optimized dex file. If null
, an OS-defined |
| 132 * path based on {@link dexFile} is used. |
| 133 * @return The ClassLoader. Returns null on an error. |
| 134 */ |
| 135 private static ClassLoader tryCreatingClassLoader( |
| 136 String canaryClassName, File dexFile, File optimizedDir) { |
| 137 try { |
| 138 ClassLoader loader = new BaseDexClassLoader( |
| 139 dexFile.getPath(), optimizedDir, null, ClassLoader.getSystem
ClassLoader()); |
| 140 // Loading {@link canaryClassName} will throw an exception if the .d
ex file cannot be |
| 141 // loaded. |
| 142 loader.loadClass(canaryClassName); |
| 143 return loader; |
| 144 } catch (Exception e) { |
| 145 String optimizedDirPath = (optimizedDir == null) ? null : optimizedD
ir.getPath(); |
| 146 Log.w(TAG, "Could not load dex from " + dexFile.getPath() + " with o
ptimized directory " |
| 147 + optimizedDirPath); |
| 148 e.printStackTrace(); |
| 149 return null; |
| 150 } |
| 151 } |
| 152 } |
OLD | NEW |