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