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;
         }
     }