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

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: fix pylint warnings 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.List;
17
18 /**
19 * Provides the ability to add native libraries and .dex files to an existing cl ass loader.
20 * Tested with Jellybean MR2 - Marshmellow.
21 */
22 final class ClassLoaderPatcher {
23 private static final String TAG = "cr.incrementalinstall";
24 private final File mAppFilesSubDir;
25 private final ClassLoader mClassLoader;
26 private final Object mLibcoreOs;
27 private final int mProcessUid;
28 final boolean mIsPrimaryProcess;
29
30 ClassLoaderPatcher(Context context) throws ReflectiveOperationException {
31 mAppFilesSubDir =
32 new File(context.getApplicationInfo().dataDir, "incremental-inst all-files");
33 mClassLoader = context.getClassLoader();
34 mLibcoreOs = Reflect.getField(Class.forName("libcore.io.Libcore"), "os") ;
35 mProcessUid = (Integer) Reflect.invokeMethod(mLibcoreOs, "getuid");
36 mIsPrimaryProcess = context.getApplicationInfo().uid == mProcessUid;
37 Log.i(TAG, "uid=" + mProcessUid + " (isPrimary=" + mIsPrimaryProcess + " )");
38 }
39
40 /**
41 * Loads all dex files within |dexDir| into the app's ClassLoader.
42 */
43 void loadDexFiles(File dexDir) throws ReflectiveOperationException, FileNotF oundException {
44 Log.i(TAG, "Installing dex files from: " + dexDir);
45 File[] dexFilesArr = dexDir.listFiles();
46 if (dexFilesArr == null) {
47 throw new FileNotFoundException("Dex dir does not exist: " + dexDir) ;
48 }
49 // The optimized dex files will be owned by this process' user.
50 // Store them within the app's data dir rather than on /data/local/tmp
51 // so that they are still deleted (by the OS) when we uninstall
52 // (even on a non-rooted device).
53 File incrementalDexesDir = new File(mAppFilesSubDir, "optimized-dexes");
54 File isolatedDexesDir = new File(mAppFilesSubDir, "isolated-dexes");
55 File optimizedDir;
56
57 if (mIsPrimaryProcess) {
58 ensureAppFilesSubDirExists();
59 // Allows isolated processes to access the same files.
60 incrementalDexesDir.mkdir();
61 incrementalDexesDir.setReadable(true, false);
62 incrementalDexesDir.setExecutable(true, false);
63 // Create a directory for isolated processes to create directories i n.
64 isolatedDexesDir.mkdir();
65 isolatedDexesDir.setWritable(true, false);
66 isolatedDexesDir.setExecutable(true, false);
67
68 optimizedDir = incrementalDexesDir;
69 } else {
70 // There is a UID check of the directory in dalvik.system.DexFile():
71 // https://android.googlesource.com/platform/libcore/+/45e0260/dalvi k/src/main/java/dalvik/system/DexFile.java#101
72 // Rather than have each isolated process run DexOpt though, we use
73 // symlinks within the directory to point at the browser process'
74 // optimized dex files.
75 optimizedDir = new File(isolatedDexesDir, "isolated-" + mProcessUid) ;
76 optimizedDir.mkdir();
77 // Always wipe it out and re-create for simplicity.
78 Log.i(TAG, "Creating dex file symlinks for isolated process");
79 for (File f : optimizedDir.listFiles()) {
80 f.delete();
81 }
82 for (File f : incrementalDexesDir.listFiles()) {
83 String to = "../../" + incrementalDexesDir.getName() + "/" + f.g etName();
84 File from = new File(optimizedDir, f.getName());
85 createSymlink(to, from);
86 }
87 }
88
89 Log.i(TAG, "Code cache dir: " + optimizedDir);
90 // TODO(agrieve): Might need to record classpath ordering if we ever hav e duplicate
91 // class names (since then order will matter here).
92 Log.i(TAG, "Loading " + dexFilesArr.length + " dex files");
93
94 Object dexPathList = Reflect.getField(mClassLoader, "pathList");
95 Object[] dexElements = (Object[]) Reflect.getField(dexPathList, "dexElem ents");
96 Object[] additionalElements = makeDexElements(dexFilesArr, optimizedDir) ;
97 Reflect.setField(
98 dexPathList, "dexElements", Reflect.concatArrays(dexElements, ad ditionalElements));
99 }
100
101 /**
102 * Sets up all libraries within |libDir| to be loadable by System.loadLibrar y().
103 */
104 void importNativeLibs(File libDir) throws ReflectiveOperationException, IOEx ception {
105 Log.i(TAG, "Importing native libraries from: " + libDir);
106 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
107 libDir = prepareNativeLibsAndroidM(libDir);
108 }
109 addNativeLibrarySearchPath(libDir);
110 }
111
112 /**
113 * Primary process: Copies native libraries into the app's data directory
114 * Other processes: Waits for primary process to finish copying.
115 */
116 private File prepareNativeLibsAndroidM(File libDir) throws IOException {
117 File localLibsDir = new File(mAppFilesSubDir, "lib");
118 File copyLibsLockFile = new File(mAppFilesSubDir, "libcopy.lock");
119 // Due to a new SELinux policy, all libs must be copied into the app's
120 // data directory first.
121 // https://code.google.com/p/android/issues/detail?id=79480
122 if (mIsPrimaryProcess) {
123 LockFile lockFile = LockFile.acquireRuntimeLock(copyLibsLockFile);
124 if (lockFile == null) {
125 LockFile.waitForRuntimeLock(copyLibsLockFile, 10 * 1000);
126 } else {
127 try {
128 ensureAppFilesSubDirExists();
129 localLibsDir.mkdir();
130 localLibsDir.setReadable(true, false);
131 localLibsDir.setExecutable(true, false);
132 copyChangedFiles(libDir, localLibsDir);
133 } finally {
134 lockFile.release();
135 }
136 }
137 } else {
138 // TODO: Work around this issue by using APK splits to install each dex / lib.
139 throw new RuntimeException("Incremental install does not work on And roid M+ "
140 + "with isolated processes. Use the gn arg:\n"
141 + " disable_incremental_isolated_processes=true\n"
142 + "and try again.");
143 }
144 return localLibsDir;
145 }
146
147 @SuppressWarnings("unchecked")
148 private void addNativeLibrarySearchPath(File nativeLibDir) throws Reflective OperationException {
149 Object dexPathList = Reflect.getField(mClassLoader, "pathList");
150 Object currentDirs = Reflect.getField(dexPathList, "nativeLibraryDirecto ries");
151 File[] newDirs = new File[] { nativeLibDir };
152 // Switched from an array to an ArrayList in Lollipop.
153 if (currentDirs instanceof List) {
154 List<File> dirsAsList = (List<File>) currentDirs;
155 dirsAsList.add(nativeLibDir);
156 } else {
157 File[] dirsAsArray = (File[]) currentDirs;
158 Reflect.setField(dexPathList, "nativeLibraryDirectories",
159 Reflect.concatArrays(dirsAsArray, newDirs));
160 }
161
162 Object[] nativeLibraryPathElements;
163 try {
164 nativeLibraryPathElements =
165 (Object[]) Reflect.getField(dexPathList, "nativeLibraryPathE lements");
166 } catch (NoSuchFieldException e) {
167 // This field doesn't exist pre-M.
168 return;
169 }
170 Object[] additionalElements = makeNativePathElements(newDirs);
171 Reflect.setField(
172 dexPathList, "nativeLibraryPathElements",
173 Reflect.concatArrays(nativeLibraryPathElements, additionalElemen ts));
174 }
175
176 private static void copyChangedFiles(File srcDir, File dstDir) throws IOExce ption {
177 // No need to delete stale libs since libraries are loaded explicitly.
178 for (File f : srcDir.listFiles()) {
179 // Note: Tried using hardlinks, but resulted in EACCES exceptions.
180 File dest = new File(dstDir, f.getName());
181 copyIfModified(f, dest);
182 }
183 }
184
185 private static void copyIfModified(File src, File dest) throws IOException {
186 long lastModified = src.lastModified();
187 if (!dest.exists() || dest.lastModified() != lastModified) {
188 Log.i(TAG, "Copying " + src + " -> " + dest);
189 FileInputStream istream = new FileInputStream(src);
190 FileOutputStream ostream = new FileOutputStream(dest);
191 ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.g etChannel().size());
192 istream.close();
193 ostream.close();
194 dest.setReadable(true, false);
195 dest.setExecutable(true, false);
196 dest.setLastModified(lastModified);
197 }
198 }
199
200 private void ensureAppFilesSubDirExists() {
201 mAppFilesSubDir.mkdir();
202 mAppFilesSubDir.setExecutable(true, false);
203 }
204
205 private void createSymlink(String to, File from) throws ReflectiveOperationE xception {
206 Reflect.invokeMethod(mLibcoreOs, "symlink", to, from.getAbsolutePath());
207 }
208
209 private static Object[] makeNativePathElements(File[] paths)
210 throws ReflectiveOperationException {
211 Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element") ;
212 Object[] entries = new Object[paths.length];
213 for (int i = 0; i < paths.length; ++i) {
214 entries[i] = Reflect.newInstance(entryClazz, paths[i], true, null, n ull);
215 }
216 return entries;
217 }
218
219 private static Object[] makeDexElements(File[] files, File optimizedDirector y)
220 throws ReflectiveOperationException {
221 Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element") ;
222 Class<?> clazz = Class.forName("dalvik.system.DexPathList");
223 Object[] entries = new Object[files.length];
224 File emptyDir = new File("");
225 for (int i = 0; i < files.length; ++i) {
226 File file = files[i];
227 Object dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, op timizedDirectory);
228 entries[i] = Reflect.newInstance(entryClazz, emptyDir, false, file, dexFile);
229 }
230 return entries;
231 }
232 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698