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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
diff --git a/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..66486c5f7ef23e7ae40e56113d087e961c21ba4d
--- /dev/null
+++ b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
@@ -0,0 +1,232 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.incrementalinstall;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Provides the ability to add native libraries and .dex files to an existing class loader.
+ * Tested with Jellybean MR2 - Marshmellow.
+ */
+final class ClassLoaderPatcher {
+ private static final String TAG = "cr.incrementalinstall";
+ private final File mAppFilesSubDir;
+ private final ClassLoader mClassLoader;
+ private final Object mLibcoreOs;
+ private final int mProcessUid;
+ final boolean mIsPrimaryProcess;
+
+ ClassLoaderPatcher(Context context) throws ReflectiveOperationException {
+ mAppFilesSubDir =
+ new File(context.getApplicationInfo().dataDir, "incremental-install-files");
+ mClassLoader = context.getClassLoader();
+ mLibcoreOs = Reflect.getField(Class.forName("libcore.io.Libcore"), "os");
+ mProcessUid = (Integer) Reflect.invokeMethod(mLibcoreOs, "getuid");
+ mIsPrimaryProcess = context.getApplicationInfo().uid == mProcessUid;
+ Log.i(TAG, "uid=" + mProcessUid + " (isPrimary=" + mIsPrimaryProcess + ")");
+ }
+
+ /**
+ * Loads all dex files within |dexDir| into the app's ClassLoader.
+ */
+ void loadDexFiles(File dexDir) throws ReflectiveOperationException, FileNotFoundException {
+ Log.i(TAG, "Installing dex files from: " + dexDir);
+ File[] dexFilesArr = dexDir.listFiles();
+ if (dexFilesArr == null) {
+ throw new FileNotFoundException("Dex dir does not exist: " + dexDir);
+ }
+ // The optimized dex files will be owned by this process' user.
+ // Store them within the app's data dir rather than on /data/local/tmp
+ // so that they are still deleted (by the OS) when we uninstall
+ // (even on a non-rooted device).
+ File incrementalDexesDir = new File(mAppFilesSubDir, "optimized-dexes");
+ File isolatedDexesDir = new File(mAppFilesSubDir, "isolated-dexes");
+ File optimizedDir;
+
+ if (mIsPrimaryProcess) {
+ ensureAppFilesSubDirExists();
+ // Allows isolated processes to access the same files.
+ incrementalDexesDir.mkdir();
+ incrementalDexesDir.setReadable(true, false);
+ incrementalDexesDir.setExecutable(true, false);
+ // Create a directory for isolated processes to create directories in.
+ isolatedDexesDir.mkdir();
+ isolatedDexesDir.setWritable(true, false);
+ isolatedDexesDir.setExecutable(true, false);
+
+ optimizedDir = incrementalDexesDir;
+ } else {
+ // There is a UID check of the directory in dalvik.system.DexFile():
+ // https://android.googlesource.com/platform/libcore/+/45e0260/dalvik/src/main/java/dalvik/system/DexFile.java#101
+ // Rather than have each isolated process run DexOpt though, we use
+ // symlinks within the directory to point at the browser process'
+ // optimized dex files.
+ optimizedDir = new File(isolatedDexesDir, "isolated-" + mProcessUid);
+ optimizedDir.mkdir();
+ // Always wipe it out and re-create for simplicity.
+ Log.i(TAG, "Creating dex file symlinks for isolated process");
+ for (File f : optimizedDir.listFiles()) {
+ f.delete();
+ }
+ for (File f : incrementalDexesDir.listFiles()) {
+ String to = "../../" + incrementalDexesDir.getName() + "/" + f.getName();
+ File from = new File(optimizedDir, f.getName());
+ createSymlink(to, from);
+ }
+ }
+
+ Log.i(TAG, "Code cache dir: " + optimizedDir);
+ // TODO(agrieve): Might need to record classpath ordering if we ever have duplicate
+ // class names (since then order will matter here).
+ Log.i(TAG, "Loading " + dexFilesArr.length + " dex files");
+
+ Object dexPathList = Reflect.getField(mClassLoader, "pathList");
+ Object[] dexElements = (Object[]) Reflect.getField(dexPathList, "dexElements");
+ Object[] additionalElements = makeDexElements(dexFilesArr, optimizedDir);
+ Reflect.setField(
+ dexPathList, "dexElements", Reflect.concatArrays(dexElements, additionalElements));
+ }
+
+ /**
+ * Sets up all libraries within |libDir| to be loadable by System.loadLibrary().
+ */
+ void importNativeLibs(File libDir) throws ReflectiveOperationException, IOException {
+ Log.i(TAG, "Importing native libraries from: " + libDir);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ libDir = prepareNativeLibsAndroidM(libDir);
+ }
+ addNativeLibrarySearchPath(libDir);
+ }
+
+ /**
+ * Primary process: Copies native libraries into the app's data directory
+ * Other processes: Waits for primary process to finish copying.
+ */
+ private File prepareNativeLibsAndroidM(File libDir) throws IOException {
+ File localLibsDir = new File(mAppFilesSubDir, "lib");
+ File copyLibsLockFile = new File(mAppFilesSubDir, "libcopy.lock");
+ // Due to a new SELinux policy, all libs must be copied into the app's
+ // data directory first.
+ // https://code.google.com/p/android/issues/detail?id=79480
+ if (mIsPrimaryProcess) {
+ LockFile lockFile = LockFile.acquireRuntimeLock(copyLibsLockFile);
+ if (lockFile == null) {
+ LockFile.waitForRuntimeLock(copyLibsLockFile, 10 * 1000);
+ } else {
+ try {
+ ensureAppFilesSubDirExists();
+ localLibsDir.mkdir();
+ localLibsDir.setReadable(true, false);
+ localLibsDir.setExecutable(true, false);
+ copyChangedFiles(libDir, localLibsDir);
+ } finally {
+ lockFile.release();
+ }
+ }
+ } else {
+ // TODO: Work around this issue by using APK splits to install each dex / lib.
+ throw new RuntimeException("Incremental install does not work on Android M+ "
+ + "with isolated processes. Use the gn arg:\n"
+ + " disable_incremental_isolated_processes=true\n"
+ + "and try again.");
+ }
+ return localLibsDir;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addNativeLibrarySearchPath(File nativeLibDir) throws ReflectiveOperationException {
+ Object dexPathList = Reflect.getField(mClassLoader, "pathList");
+ Object currentDirs = Reflect.getField(dexPathList, "nativeLibraryDirectories");
+ File[] newDirs = new File[] { nativeLibDir };
+ // Switched from an array to an ArrayList in Lollipop.
+ if (currentDirs instanceof List) {
+ List<File> dirsAsList = (List<File>) currentDirs;
+ dirsAsList.add(nativeLibDir);
+ } else {
+ File[] dirsAsArray = (File[]) currentDirs;
+ Reflect.setField(dexPathList, "nativeLibraryDirectories",
+ Reflect.concatArrays(dirsAsArray, newDirs));
+ }
+
+ Object[] nativeLibraryPathElements;
+ try {
+ nativeLibraryPathElements =
+ (Object[]) Reflect.getField(dexPathList, "nativeLibraryPathElements");
+ } catch (NoSuchFieldException e) {
+ // This field doesn't exist pre-M.
+ return;
+ }
+ Object[] additionalElements = makeNativePathElements(newDirs);
+ Reflect.setField(
+ dexPathList, "nativeLibraryPathElements",
+ Reflect.concatArrays(nativeLibraryPathElements, additionalElements));
+ }
+
+ private static void copyChangedFiles(File srcDir, File dstDir) throws IOException {
+ // No need to delete stale libs since libraries are loaded explicitly.
+ for (File f : srcDir.listFiles()) {
+ // Note: Tried using hardlinks, but resulted in EACCES exceptions.
+ File dest = new File(dstDir, f.getName());
+ copyIfModified(f, dest);
+ }
+ }
+
+ private static void copyIfModified(File src, File dest) throws IOException {
+ long lastModified = src.lastModified();
+ if (!dest.exists() || dest.lastModified() != lastModified) {
+ Log.i(TAG, "Copying " + src + " -> " + dest);
+ FileInputStream istream = new FileInputStream(src);
+ FileOutputStream ostream = new FileOutputStream(dest);
+ ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.getChannel().size());
+ istream.close();
+ ostream.close();
+ dest.setReadable(true, false);
+ dest.setExecutable(true, false);
+ dest.setLastModified(lastModified);
+ }
+ }
+
+ private void ensureAppFilesSubDirExists() {
+ mAppFilesSubDir.mkdir();
+ mAppFilesSubDir.setExecutable(true, false);
+ }
+
+ private void createSymlink(String to, File from) throws ReflectiveOperationException {
+ Reflect.invokeMethod(mLibcoreOs, "symlink", to, from.getAbsolutePath());
+ }
+
+ private static Object[] makeNativePathElements(File[] paths)
+ throws ReflectiveOperationException {
+ Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element");
+ Object[] entries = new Object[paths.length];
+ for (int i = 0; i < paths.length; ++i) {
+ entries[i] = Reflect.newInstance(entryClazz, paths[i], true, null, null);
+ }
+ return entries;
+ }
+
+ private static Object[] makeDexElements(File[] files, File optimizedDirectory)
+ throws ReflectiveOperationException {
+ Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element");
+ Class<?> clazz = Class.forName("dalvik.system.DexPathList");
+ Object[] entries = new Object[files.length];
+ File emptyDir = new File("");
+ for (int i = 0; i < files.length; ++i) {
+ File file = files[i];
+ Object dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, optimizedDirectory);
+ entries[i] = Reflect.newInstance(entryClazz, emptyDir, false, file, dexFile);
+ }
+ return entries;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698