Merge "Precreate the classloader for the WebView." am: 566b1c80e4
am: 1feb782f01
Change-Id: Iec0bf848e4c25afc6a6b9ee83339a03f314adee9
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 6a73829..ef2db4a 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -16,17 +16,19 @@
package android.app;
+import android.os.Build;
import android.os.Trace;
import android.util.ArrayMap;
import com.android.internal.os.PathClassLoaderFactory;
import dalvik.system.PathClassLoader;
-class ApplicationLoaders {
+/** @hide */
+public class ApplicationLoaders {
public static ApplicationLoaders getDefault() {
return gApplicationLoaders;
}
- public ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
+ ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent) {
/*
@@ -80,6 +82,19 @@
}
}
+ /**
+ * Creates a classloader for the WebView APK and places it in the cache of loaders maintained
+ * by this class. This is used in the WebView zygote, where its presence in the cache speeds up
+ * startup and enables memory sharing.
+ */
+ public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath) {
+ // The correct paths are calculated by WebViewZygote in the system server and passed to
+ // us here. We hardcode the other parameters: WebView always targets the current SDK,
+ // does not need to use non-public system libraries, and uses the base classloader as its
+ // parent to permit usage of the cache.
+ return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null);
+ }
+
private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
/**
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 7754244..9ea16a7 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -339,39 +339,43 @@
* concatenation of both apps' shared library lists.
*/
- String instrumentationPackageName = activityThread.mInstrumentationPackageName;
- String instrumentationAppDir = activityThread.mInstrumentationAppDir;
- String[] instrumentationSplitAppDirs = activityThread.mInstrumentationSplitAppDirs;
- String instrumentationLibDir = activityThread.mInstrumentationLibDir;
-
- String instrumentedAppDir = activityThread.mInstrumentedAppDir;
- String[] instrumentedSplitAppDirs = activityThread.mInstrumentedSplitAppDirs;
- String instrumentedLibDir = activityThread.mInstrumentedLibDir;
String[] instrumentationLibs = null;
+ // activityThread will be null when called from the WebView zygote; just assume
+ // no instrumentation applies in this case.
+ if (activityThread != null) {
+ String instrumentationPackageName = activityThread.mInstrumentationPackageName;
+ String instrumentationAppDir = activityThread.mInstrumentationAppDir;
+ String[] instrumentationSplitAppDirs = activityThread.mInstrumentationSplitAppDirs;
+ String instrumentationLibDir = activityThread.mInstrumentationLibDir;
- if (appDir.equals(instrumentationAppDir)
- || appDir.equals(instrumentedAppDir)) {
- outZipPaths.clear();
- outZipPaths.add(instrumentationAppDir);
- if (instrumentationSplitAppDirs != null) {
- Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
- }
- if (!instrumentationAppDir.equals(instrumentedAppDir)) {
- outZipPaths.add(instrumentedAppDir);
- if (instrumentedSplitAppDirs != null) {
- Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+ String instrumentedAppDir = activityThread.mInstrumentedAppDir;
+ String[] instrumentedSplitAppDirs = activityThread.mInstrumentedSplitAppDirs;
+ String instrumentedLibDir = activityThread.mInstrumentedLibDir;
+
+ if (appDir.equals(instrumentationAppDir)
+ || appDir.equals(instrumentedAppDir)) {
+ outZipPaths.clear();
+ outZipPaths.add(instrumentationAppDir);
+ if (instrumentationSplitAppDirs != null) {
+ Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
}
- }
-
- if (outLibPaths != null) {
- outLibPaths.add(instrumentationLibDir);
- if (!instrumentationLibDir.equals(instrumentedLibDir)) {
- outLibPaths.add(instrumentedLibDir);
+ if (!instrumentationAppDir.equals(instrumentedAppDir)) {
+ outZipPaths.add(instrumentedAppDir);
+ if (instrumentedSplitAppDirs != null) {
+ Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+ }
}
- }
- if (!instrumentedAppDir.equals(instrumentationAppDir)) {
- instrumentationLibs = getLibrariesFor(instrumentationPackageName);
+ if (outLibPaths != null) {
+ outLibPaths.add(instrumentationLibDir);
+ if (!instrumentationLibDir.equals(instrumentedLibDir)) {
+ outLibPaths.add(instrumentedLibDir);
+ }
+ }
+
+ if (!instrumentedAppDir.equals(instrumentationAppDir)) {
+ instrumentationLibs = getLibrariesFor(instrumentationPackageName);
+ }
}
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index f41a838..f1e8fc2d 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -56,7 +56,9 @@
@SystemApi
public final class WebViewFactory {
- private static final String CHROMIUM_WEBVIEW_FACTORY =
+ // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
+ /** @hide */
+ public static final String CHROMIUM_WEBVIEW_FACTORY =
"com.android.webview.chromium.WebViewChromiumFactoryProvider";
private static final String NULL_WEBVIEW_FACTORY =
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index bc6e7b4..c206974 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -16,14 +16,19 @@
package android.webkit;
+import android.app.LoadedApk;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.SystemService;
import android.os.ZygoteProcess;
+import android.text.TextUtils;
import android.util.Log;
+import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.TimeoutException;
/** @hide */
@@ -122,11 +127,21 @@
try {
sZygote = new ZygoteProcess("webview_zygote", null);
- String packagePath = sPackage.applicationInfo.sourceDir;
- String libsPath = sPackage.applicationInfo.nativeLibraryDir;
+ // All the work below is usually done by LoadedApk, but the zygote can't talk to
+ // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
+ // doesn't have an ActivityThread and can't use Binder.
+ // Instead, figure out the paths here, in the system server where we have access to
+ // the package manager. Reuse the logic from LoadedApk to determine the correct
+ // paths and pass them to the zygote as strings.
+ final List<String> zipPaths = new ArrayList<>(10);
+ final List<String> libPaths = new ArrayList<>(10);
+ LoadedApk.makePaths(null, sPackage.applicationInfo, zipPaths, libPaths);
+ final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
+ final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
+ TextUtils.join(File.pathSeparator, zipPaths);
- Log.d(LOGTAG, "Preloading package " + packagePath + " " + libsPath);
- sZygote.preloadPackageForAbi(packagePath, libsPath, Build.SUPPORTED_ABIS[0]);
+ Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
+ sZygote.preloadPackageForAbi(zip, librarySearchPath, Build.SUPPORTED_ABIS[0]);
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to " + serviceName, e);
sZygote = null;
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 11dd0e8..d968e3c 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -16,14 +16,17 @@
package com.android.internal.os;
+import android.app.ApplicationLoaders;
import android.net.LocalSocket;
import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.Log;
+import android.webkit.WebViewFactory;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
/**
* Startup class for the WebView zygote process.
@@ -52,7 +55,27 @@
@Override
protected boolean handlePreloadPackage(String packagePath, String libsPath) {
- // TODO: Use preload information to setup the ClassLoader.
+ // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
+ // our children will reuse the same classloader instead of creating their own.
+ // This enables us to preload Java and native code in the webview zygote process and
+ // have the preloaded versions actually be used post-fork.
+ ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
+ packagePath, libsPath);
+
+ // Once we have the classloader, look up the WebViewFactoryProvider implementation and
+ // call preloadInZygote() on it to give it the opportunity to preload the native library
+ // and perform any other initialisation work that should be shared among the children.
+ try {
+ Class providerClass = Class.forName(WebViewFactory.CHROMIUM_WEBVIEW_FACTORY, true,
+ loader);
+ Object result = providerClass.getMethod("preloadInZygote").invoke(null);
+ if (!((Boolean)result).booleanValue()) {
+ Log.e(TAG, "preloadInZygote returned false");
+ }
+ } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
+ IllegalAccessException | InvocationTargetException e) {
+ Log.e(TAG, "Exception while preloading package", e);
+ }
return false;
}
}