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