OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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.incrementalinstall; | |
6 | |
7 import android.content.Context; | |
8 import android.os.Build; | |
9 import android.util.Log; | |
10 | |
11 import java.io.File; | |
12 import java.io.FileInputStream; | |
13 import java.io.FileNotFoundException; | |
14 import java.io.FileOutputStream; | |
15 import java.io.IOException; | |
16 import java.util.ArrayList; | |
17 import java.util.Arrays; | |
18 import java.util.List; | |
19 | |
20 /** | |
21 * Provides the ability to add native libraries and .dex files to an existing cl ass loader. | |
22 * Tested with Jellybean MR2 - Marshmellow. | |
23 */ | |
24 final class ClassLoaderPatcher { | |
25 private static final String TAG = "cr.incrementalinstall"; | |
26 private final File mAppFilesSubDir; | |
27 private final ClassLoader mClassLoader; | |
28 private final Object mLibcoreOs; | |
29 private final int mProcessUid; | |
30 final boolean mIsPrimaryProcess; | |
31 | |
32 ClassLoaderPatcher(Context context) throws ReflectiveOperationException { | |
nyquist
2015/09/16 06:30:46
ReflectiveOperationException was added in API leve
agrieve
2015/09/16 14:59:59
You are correct!
http://stackoverflow.com/question
| |
33 mAppFilesSubDir = | |
34 new File(context.getApplicationInfo().dataDir, "incremental-inst all-files"); | |
35 mClassLoader = context.getClassLoader(); | |
36 mLibcoreOs = Reflect.getField(Class.forName("libcore.io.Libcore"), "os") ; | |
37 mProcessUid = (Integer) Reflect.invokeMethod(mLibcoreOs, "getuid"); | |
nyquist
2015/09/16 06:30:46
I wish we didn't have to do this hack to call getu
agrieve
2015/09/16 14:59:59
There's also: https://code.google.com/p/chromium/c
| |
38 mIsPrimaryProcess = context.getApplicationInfo().uid == mProcessUid; | |
39 Log.i(TAG, "uid=" + mProcessUid + " (isPrimary=" + mIsPrimaryProcess + " )"); | |
40 } | |
41 | |
42 /** | |
43 * Loads all dex files within |dexDir| into the app's ClassLoader. | |
44 */ | |
45 void loadDexFiles(File dexDir) throws ReflectiveOperationException, FileNotF oundException { | |
46 Log.i(TAG, "Installing dex files from: " + dexDir); | |
47 File[] dexFilesArr = dexDir.listFiles(); | |
48 if (dexFilesArr == null) { | |
49 throw new FileNotFoundException("Dex dir does not exist: " + dexDir) ; | |
50 } | |
51 // The optimized dex files will be owned by this process' user. | |
52 // Store them within the app's data dir rather than on /data/local/tmp | |
53 // so that they are still deleted (by the OS) when we uninstall | |
54 // (even on a non-rooted device). | |
55 File incrementalDexesDir = new File(mAppFilesSubDir, "optimized-dexes"); | |
56 File isolatedDexesDir = new File(mAppFilesSubDir, "isolated-dexes"); | |
57 File optimizedDir; | |
58 | |
59 if (mIsPrimaryProcess) { | |
60 ensureAppFilesSubDirExists(); | |
61 // Allows isolated processes to access the same files. | |
62 incrementalDexesDir.mkdir(); | |
nyquist
2015/09/16 06:30:45
Nit: What if this fails? (same below)
agrieve
2015/09/16 14:59:59
Then things won't work :P.
It fails silently if
| |
63 incrementalDexesDir.setReadable(true, false); | |
64 incrementalDexesDir.setExecutable(true, false); | |
65 // Create a directory for isolated processes to create directories i n. | |
66 isolatedDexesDir.mkdir(); | |
67 isolatedDexesDir.setWritable(true, false); | |
68 isolatedDexesDir.setExecutable(true, false); | |
69 | |
70 optimizedDir = incrementalDexesDir; | |
71 } else { | |
72 // There is a UID check of the directory in dalvik.system.DexFile(): | |
73 // https://android.googlesource.com/platform/libcore/+/45e0260/dalvi k/src/main/java/dalvik/system/DexFile.java#101 | |
74 // Rather than have each isolated process run DexOpt though, we use | |
75 // symlinks within the directory to point at the browser process' | |
76 // optimized dex files. | |
77 optimizedDir = new File(isolatedDexesDir, "isolated-" + mProcessUid) ; | |
78 optimizedDir.mkdir(); | |
nyquist
2015/09/16 06:30:46
Nit: This might fail and therefore returns a boole
agrieve
2015/09/16 14:59:59
I'm a bit afraid that adding a lot of explicit che
| |
79 // Always wipe it out and re-create for simplicity. | |
80 Log.i(TAG, "Creating dex file symlinks for isolated process"); | |
81 for (File f : optimizedDir.listFiles()) { | |
82 f.delete(); | |
nyquist
2015/09/16 06:30:46
Nit: This operation might fail, and therefore it r
agrieve
2015/09/16 14:59:59
Having stale symlinks around is harmless. If one f
| |
83 } | |
84 for (File f : incrementalDexesDir.listFiles()) { | |
85 String to = "../../" + incrementalDexesDir.getName() + "/" + f.g etName(); | |
86 File from = new File(optimizedDir, f.getName()); | |
87 createSymlink(to, from); | |
88 } | |
89 } | |
90 | |
91 Log.i(TAG, "Code cache dir: " + optimizedDir); | |
92 // TODO(agrieve): Might need to recored classpath ordering if we ever ha ve duplicate | |
nyquist
2015/09/16 06:30:46
Nit: record*
agrieve
2015/09/16 14:59:59
Done.
| |
93 // class names (since then order will matter here). | |
94 ArrayList<File> dexFiles = new ArrayList<File>(Arrays.asList(dexFilesArr )); | |
nyquist
2015/09/16 06:30:46
This seems to only be used to print dexFiles.size(
agrieve
2015/09/16 14:59:59
One iteration did use a list, but this certainly d
| |
95 Log.i(TAG, "Loading " + dexFiles.size() + " dex files"); | |
96 | |
97 Object dexPathList = Reflect.getField(mClassLoader, "pathList"); | |
98 Object[] dexElements = (Object[]) Reflect.getField(dexPathList, "dexElem ents"); | |
99 Object[] additionalElements = makeDexElements(dexFilesArr, optimizedDir) ; | |
100 Reflect.setField( | |
101 dexPathList, "dexElements", Reflect.concatArrays(dexElements, ad ditionalElements)); | |
102 } | |
103 | |
104 /** | |
105 * Sets up all libraries within |libDir| to be loadable by System.loadLibrar y(). | |
106 */ | |
107 void importNativeLibs(File libDir) throws ReflectiveOperationException, IOEx ception { | |
108 Log.i(TAG, "Importing native libraries from: " + libDir); | |
109 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
110 File localLibsDir = new File(mAppFilesSubDir, "lib"); | |
nyquist
2015/09/16 06:30:46
Nit: Could you extract this block to something lik
agrieve
2015/09/16 14:59:58
Done.
| |
111 File copyLibsLockFile = new File(mAppFilesSubDir, "libcopy.lock"); | |
112 // Due to a new SELinux policy, all libs must be copied into the app 's | |
113 // data directory first. | |
114 // https://code.google.com/p/android/issues/detail?id=79480 | |
115 if (mIsPrimaryProcess) { | |
116 LockFile lockFile = LockFile.acquireRuntimeLock(copyLibsLockFile ); | |
117 if (lockFile != null) { | |
nyquist
2015/09/16 06:30:46
Nit: Could you flip these if-blocks, so it reads i
agrieve
2015/09/16 14:59:59
Done.
| |
118 try { | |
119 copyNativeLibraryFiles(libDir, localLibsDir); | |
120 } finally { | |
121 lockFile.release(); | |
122 } | |
123 } else { | |
124 LockFile.waitForRuntimeLock(copyLibsLockFile, 10 * 1000); | |
125 } | |
126 } else { | |
127 throw new RuntimeException("Incremental install does not work on Android M+ " | |
128 + "with isolated processes. Use the gn arg:\n" | |
129 + " disable_incremental_isolated_processes=true\n" | |
130 + "and try again."); | |
131 } | |
132 libDir = localLibsDir; | |
nyquist
2015/09/16 06:30:46
Nit: This just looks odd to me (reassignment of pa
agrieve
2015/09/16 14:59:58
Looks a bit better now with method extracted :)
| |
133 } | |
134 addNativeLibrarySearchPath(libDir); | |
135 } | |
136 | |
137 @SuppressWarnings("unchecked") | |
138 private void addNativeLibrarySearchPath(File nativeLibDir) throws Reflective OperationException { | |
139 Object dexPathList = Reflect.getField(mClassLoader, "pathList"); | |
140 Object currentDirs = Reflect.getField(dexPathList, "nativeLibraryDirecto ries"); | |
141 File[] newDirs = new File[] { nativeLibDir }; | |
142 // Switched from an array to an ArrayList in Lollipop. | |
143 if (currentDirs instanceof List) { | |
144 List<File> dirsAsList = (List<File>) currentDirs; | |
145 dirsAsList.add(nativeLibDir); | |
146 } else { | |
147 File[] dirsAsArray = (File[]) currentDirs; | |
148 Reflect.setField(dexPathList, "nativeLibraryDirectories", | |
149 Reflect.concatArrays(dirsAsArray, newDirs)); | |
150 } | |
151 | |
152 Object[] nativeLibraryPathElements = null; | |
nyquist
2015/09/16 06:30:46
Nit: This null-assignment is unnecessary. It's alw
agrieve
2015/09/16 14:59:59
Done.
| |
153 try { | |
154 nativeLibraryPathElements = | |
155 (Object[]) Reflect.getField(dexPathList, "nativeLibraryPathE lements"); | |
156 } catch (NoSuchFieldException e) { | |
157 // This field doesn't exist pre-M. | |
158 return; | |
159 } | |
160 Object[] additionalElements = makeNativePathElements(newDirs); | |
161 Reflect.setField( | |
162 dexPathList, "nativeLibraryPathElements", | |
163 Reflect.concatArrays(nativeLibraryPathElements, additionalElemen ts)); | |
164 } | |
165 | |
166 private void copyNativeLibraryFiles(File srcDir, File dstDir) throws IOExcep tion { | |
167 ensureAppFilesSubDirExists(); | |
168 dstDir.mkdir(); | |
nyquist
2015/09/16 06:30:45
Nit: These three operations can also fail. What wo
agrieve
2015/09/16 14:59:59
Acknowledged.
| |
169 dstDir.setReadable(true, false); | |
170 dstDir.setExecutable(true, false); | |
171 | |
172 // No need to delete stale libs since libraries are loaded explicitly. | |
173 for (File f : srcDir.listFiles()) { | |
174 // Note: Tried using hardlinks, but results in EACCES exception. | |
175 File dest = new File(dstDir, f.getName()); | |
176 copyIfModified(f, dest); | |
177 } | |
178 } | |
179 | |
180 private static void copyIfModified(File src, File dest) throws IOException { | |
181 long lastModified = src.lastModified(); | |
182 if (!dest.exists() || dest.lastModified() != lastModified) { | |
183 Log.i(TAG, "Copying " + src + " -> " + dest); | |
184 FileInputStream istream = new FileInputStream(src); | |
185 FileOutputStream ostream = new FileOutputStream(dest); | |
186 ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.g etChannel().size()); | |
187 istream.close(); | |
188 ostream.close(); | |
189 dest.setReadable(true, false); | |
nyquist
2015/09/16 06:30:45
Nit: Same as above for these three.
agrieve
2015/09/16 14:59:58
Acknowledged.
| |
190 dest.setExecutable(true, false); | |
191 dest.setLastModified(lastModified); | |
192 } | |
193 } | |
194 | |
195 private void ensureAppFilesSubDirExists() { | |
196 mAppFilesSubDir.mkdir(); | |
nyquist
2015/09/16 06:30:45
Nit: Same as above for these two.
agrieve
2015/09/16 14:59:59
Acknowledged.
| |
197 mAppFilesSubDir.setExecutable(true, false); | |
198 } | |
199 | |
200 private void createSymlink(String to, File from) throws ReflectiveOperationE xception { | |
201 Reflect.invokeMethod(mLibcoreOs, "symlink", to, from.getAbsolutePath()); | |
202 } | |
203 | |
204 private static Object[] makeNativePathElements(File[] paths) | |
205 throws ReflectiveOperationException { | |
206 Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element") ; | |
207 Object[] entries = new Object[paths.length]; | |
208 for (int i = 0; i < paths.length; ++i) { | |
209 entries[i] = Reflect.newInstance(entryClazz, paths[i], true, null, n ull); | |
210 } | |
211 return entries; | |
212 } | |
213 | |
214 private static Object[] makeDexElements(File[] files, File optimizedDirector y) | |
215 throws ReflectiveOperationException { | |
216 Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element") ; | |
217 Class<?> clazz = Class.forName("dalvik.system.DexPathList"); | |
218 Object[] entries = new Object[files.length]; | |
219 File emptyDir = new File(""); | |
220 for (int i = 0; i < files.length; ++i) { | |
221 File file = files[i]; | |
222 Object dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, op timizedDirectory); | |
223 entries[i] = Reflect.newInstance(entryClazz, emptyDir, false, file, dexFile); | |
224 } | |
225 return entries; | |
226 } | |
227 } | |
OLD | NEW |