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.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 |