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.lib.client; |
| 6 |
| 7 import android.os.Build; |
| 8 import android.util.Log; |
| 9 |
| 10 import dalvik.system.DexClassLoader; |
| 11 import dalvik.system.DexFile; |
| 12 |
| 13 import org.chromium.base.annotations.SuppressFBWarnings; |
| 14 |
| 15 import java.io.File; |
| 16 import java.io.IOException; |
| 17 import java.lang.reflect.InvocationTargetException; |
| 18 import java.lang.reflect.Method; |
| 19 |
| 20 /** |
| 21 * This class provides a method to optimize .dex files. |
| 22 * Note: This class is copied (mostly) verbatim from DexOptUtils in GMSCore. |
| 23 */ |
| 24 public class DexOptimizer { |
| 25 private static final String TAG = "cr_DexOptimzer"; |
| 26 |
| 27 private static final String DEX_SUFFIX = ".dex"; |
| 28 private static final String ODEX_SUFFIX = ".odex"; |
| 29 |
| 30 /** |
| 31 * Creates optimized odex file for the specified dex file. |
| 32 * @param dexFile Path to a dex file. |
| 33 * @return True if the dex file was successfully optimized. |
| 34 */ |
| 35 @SuppressFBWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED") |
| 36 public static boolean optimize(File dexFile) { |
| 37 if (!dexFile.exists()) { |
| 38 Log.e(TAG, "Dex file does not exist! " + dexFile.getAbsolutePath()); |
| 39 return false; |
| 40 } |
| 41 |
| 42 try { |
| 43 if (!DexFile.isDexOptNeeded(dexFile.getAbsolutePath())) { |
| 44 return true; |
| 45 } |
| 46 } catch (Exception e) { |
| 47 Log.e(TAG, "Failed to check optimization status: " + e.toString() +
" : " |
| 48 + e.getMessage()); |
| 49 } |
| 50 |
| 51 File odexDir = null; |
| 52 try { |
| 53 odexDir = ensureOdexDirectory(dexFile); |
| 54 } catch (IOException e) { |
| 55 Log.e(TAG, "Failed to create odex directory! " + e.getMessage()); |
| 56 return false; |
| 57 } |
| 58 |
| 59 File generatedDexDir = odexDir; |
| 60 if (generatedDexDir.equals(dexFile.getParentFile())) { |
| 61 generatedDexDir = new File(odexDir, "optimized"); |
| 62 if (!generatedDexDir.exists() && !generatedDexDir.mkdirs()) { |
| 63 return false; |
| 64 } |
| 65 } |
| 66 |
| 67 new DexClassLoader( |
| 68 dexFile.getAbsolutePath(), |
| 69 generatedDexDir.getAbsolutePath(), |
| 70 null, |
| 71 ClassLoader.getSystemClassLoader()); |
| 72 File optimizedFile = new File(generatedDexDir, dexFile.getName()); |
| 73 if (!optimizedFile.exists()) { |
| 74 Log.e(TAG, "Failed to create dex."); |
| 75 return false; |
| 76 } |
| 77 |
| 78 File destOdexFile = new File(odexDir, replaceExtension(optimizedFile.get
Name(), "odex")); |
| 79 if (!optimizedFile.renameTo(destOdexFile)) { |
| 80 Log.e(TAG, "Failed to rename optimized file."); |
| 81 return false; |
| 82 } |
| 83 |
| 84 if (!destOdexFile.setReadable(true, false)) { |
| 85 Log.e(TAG, "Failed to make odex world readable."); |
| 86 return false; |
| 87 } |
| 88 |
| 89 return true; |
| 90 } |
| 91 |
| 92 /** |
| 93 * Guesses the directory that DexClassLoader looks in for the odex file base
d on the |
| 94 * Android OS version and the dex path. |
| 95 * @param dexPath |
| 96 * @return Guess for the default odex directory. |
| 97 */ |
| 98 private static File odexDirectory(File dexPath) { |
| 99 int currentApiVersion = Build.VERSION.SDK_INT; |
| 100 try { |
| 101 if (currentApiVersion >= Build.VERSION_CODES.M) { |
| 102 return new File( |
| 103 dexPath.getParentFile(), "oat/" + VMRuntime.getCurrentIn
structionSet()); |
| 104 } else if (currentApiVersion >= Build.VERSION_CODES.LOLLIPOP) { |
| 105 return new File(dexPath.getParentFile(), VMRuntime.getCurrentIns
tructionSet()); |
| 106 } else { |
| 107 return dexPath.getParentFile(); |
| 108 } |
| 109 } catch (NoSuchMethodException e) { |
| 110 return null; |
| 111 } |
| 112 } |
| 113 |
| 114 /** |
| 115 * Guesses the directory that DexClassLoader looks in for the odex file base
d on the |
| 116 * Android OS version and the dex path. Creates the directory if it does not
exist. |
| 117 * @param dexPath |
| 118 * @return Guess for the default odex directory. |
| 119 */ |
| 120 private static File ensureOdexDirectory(File dexPath) throws IOException { |
| 121 File odexDir = odexDirectory(dexPath); |
| 122 if (odexDir == null) { |
| 123 throw new IOException("Failed to create odex cache directory. " |
| 124 + "Could not determine odex directory."); |
| 125 } |
| 126 if (!odexDir.exists()) { |
| 127 boolean success = odexDir.mkdirs(); |
| 128 if (!success) { |
| 129 throw new IOException( |
| 130 "Failed to create odex cache directory in data directory
."); |
| 131 } |
| 132 // The full path to the odex must be traversable. |
| 133 File root = dexPath.getParentFile(); |
| 134 File dir = odexDir; |
| 135 while (dir != null && !root.equals(dir)) { |
| 136 if (!dir.setExecutable(true, false)) { |
| 137 throw new IOException("Failed to make odex directory world t
raversable: " |
| 138 + dir.getAbsolutePath()); |
| 139 } |
| 140 dir = dir.getParentFile(); |
| 141 } |
| 142 } |
| 143 return odexDir; |
| 144 } |
| 145 |
| 146 /** |
| 147 * Replaces a file name's extension. |
| 148 * |
| 149 * @param name File name to modify. |
| 150 * @param extension New extension. |
| 151 * @return File name with new extension. |
| 152 */ |
| 153 private static String replaceExtension(String name, String extension) { |
| 154 int lastDot = name.lastIndexOf("."); |
| 155 StringBuilder sb = new StringBuilder(lastDot + extension.length()); |
| 156 sb.append(name, 0, lastDot + 1); |
| 157 sb.append(extension); |
| 158 return sb.toString(); |
| 159 } |
| 160 |
| 161 /** |
| 162 * Makes use of a hidden API to retrieve the instruction set name for the cu
rrently |
| 163 * executing process. This string is used to form the directory name for the
generated |
| 164 * odex. |
| 165 * |
| 166 * - This API is not available on pre-L devices, but as the pre-L runtime di
d not scope odex |
| 167 * files by <isa> on pre-L, this is not a problem. |
| 168 * |
| 169 * - For devices L+, it's still possible for this API to be missing. In that
case |
| 170 * we will fallback to A) interpretation, and failing that B) generate an
odex in the |
| 171 * client's file space. |
| 172 */ |
| 173 private static class VMRuntime { |
| 174 @SuppressWarnings("unchecked") |
| 175 public static String getCurrentInstructionSet() throws NoSuchMethodExcep
tion { |
| 176 Method getCurrentInstructionSetMethod; |
| 177 try { |
| 178 Class c = Class.forName("dalvik.system.VMRuntime"); |
| 179 getCurrentInstructionSetMethod = c.getDeclaredMethod("getCurrent
InstructionSet"); |
| 180 } catch (ClassNotFoundException | NoSuchMethodException e) { |
| 181 Log.w(TAG, "dalvik.system.VMRuntime#getCurrentInstructionSet is
unsupported.", e); |
| 182 throw new NoSuchMethodException( |
| 183 "dalvik.system.VMRuntime#getCurrentInstructionSet could
not be found."); |
| 184 } |
| 185 try { |
| 186 return (String) getCurrentInstructionSetMethod.invoke(null); |
| 187 } catch (IllegalAccessException | InvocationTargetException e) { |
| 188 Log.w(TAG, "Failed to call dalvik.system.VMRuntime#getCurrentIns
tructionSet", e); |
| 189 throw new NoSuchMethodException( |
| 190 "dalvik.system.VMRuntime#getCurrentInstructionSet could
not be found."); |
| 191 } |
| 192 } |
| 193 } |
| 194 } |
OLD | NEW |