Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(425)

Side by Side Diff: build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java

Issue 1338813003: GN: Side-load dex files as well as native code in incremental installs (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Python review comments 1 Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698